Vue.js Fullpage Scroll Component: Dynamic scrolling for your website
In this article, you’ll learn how to code a custom Vue.js Fullpage Scroll component for your Vue.js app and add dynamic scrolling to your website.
Fullpage Scroll means that you don’t scroll normally, but you are always scrolled directly from one section to the next, or to the previous section. That’s why I’ll show you a demo of the Vue.js Fullpage Scroll component first.
Pretty Vue.js fullpage demo
So that it is clear right from the start how the Vue.js Fullpage Scroll (or OnePage Scroll) will look like, I have prepared a demo here. This version is supported by all common modern browsers and also works on mobile devices.
If you’re still looking for design inspirations, I have two more pages here that have, in my opinion, set the full page scroll extremely well in scene:
Why not use a ready-made Vue.js fullpage scroll component?
You are very welcome to do that to save yourself work. The existing ones work great! However, it is much cooler to program the whole thing yourself, isn’t it? 😉 And also you don’t have so many functions on your site, which you often don’t need.
If you’re not a friend of Vue.js, you can do the same with React, AngularJS or jQuery. The procedure is mostly similar. Individual functions can of course be adapted to the frameworks.
If you’re new to Vue.js, you can program these Vue.js examples to improve your skills!
Step 1: Create HTML Structure
First we need a container in which our Vue.js App runs. Therefore we create a container #app
. There the rest of the HTML comes in.
<div id="app"> <!-- CONTENT HERE --> </div>
Next, we create our individual sections. There you can then pack in and design any content you like.
<section class="fullpage"> <h1>Section 1</h1> </section> <section class="fullpage"> <h1>Section 2</h1> </section> <section class="fullpage"> <h1>Section 3</h1> </section> <section class="fullpage"> <h1>Section 4</h1> </section> <section class="fullpage"> <h1>Section 5</h1> </section>
In the next step we want to create the side menu. This allows you to navigate to another section by clicking on it and the active section is always displayed.
<div class="sections-menu"> <span class="menu-point" v-bind:class="{active: activeSection == index}" v-on:click="scrollToSection(index)" v-for="(offset, index) in offsets" v-bind:key="index" v-title="'Go to section ' + (index+1)"> </span> </div>
To this block it now needs some explanation: To make the menu we make a wrapper .sections-menu
. In it is a span tag which contains some attributes for Vue.js. So here is the exact explanation:
Attribute | Value | Description |
---|---|---|
v-bind:class | {active: activeSection == index} | The tag gets the class active if the current loop pass is equal to the active section. |
v-on:click | scrollToSection(index) | Clicking on the link calls the scrollToSection() function. The parameter is the loop through and therefore our Section ID. |
v-for | (offset, index) in offsets | The link is repeated according to the number of elements in the offsets array. The variable index is our Section ID. |
v-bind:key | index | To keep each loop pass unique, we set our section ID as the key for the for directive. |
v-title | ‘Go to section ‘ + (index+1) | Sets a title when hovering with the number of the section. |
Step 2: Style Sections (CSS)
In order for the effect of the full page scroll to come into its own, the individual sections should be at least 100vh (i.e. exactly screen height). So you have to see that the content of a section is adjusted to exactly this height.
.fullpage { height: 100vh; width: 100%; }
In our example, we only have a heading and subheading. For inspiration take a look at the demos linked above.
Navigation
I also kept the navigation menu as simple as possible. White points, where the active point is stronger and bigger. In addition, the menu is always on the right side of the screen. The CSS looks like this:
.sections-menu { position: fixed; right: 1rem; top: 50%; transform: translateY(-50%); } .sections-menu .menu-point { width: 10px; height: 10px; background-color: #FFF; display: block; margin: 1rem 0; opacity: .6; transition: .4s ease all; } .sections-menu .menu-point.active { opacity: 1; transform: scale(1.5); } .sections-menu .menu-point:hover { opacity: 1; transform: scale(1.2); }
CSS for positioning the font, etc. I did not discuss here, because they are basics and have nothing to do with the actual feature. You can find the complete code on my pen on Codepen.
Step 3: Programming functionalities in Vue.js/JavaScript
The JavaScript has been the most elaborate thing here. We have to cover all modern browsers as well as mobile devices.
Initialize Vue.js App
We start initializing the Vue.js App. We have already created the #app
container in the HTML.
var app = new Vue({ el: '#app', });
Declaring and Initializing Variables
Now we declare and initialize the variables. Later it becomes clear which variable we need, if the name doesn’t already tell us.
data: { inMove: false, inMoveDelay: 400, activeSection: 0, offsets: [], touchStartY: 0 }
Calculate offsets
Now we want to calculate the offsets (upper edge of the sections) of the individual sections. This gives us the advantage that we save computing power, because we don’t have to recalculate it every time we scroll.
We run a loop over all section
elements and store the offset in our global offsets
array.
calculateSectionOffsets() { let sections = document.getElementsByTagName('section'); let length = sections.length; for (let i = 0; i < length; i++) { let sectionOffset = sections[i].offsetTop; this.offsets.push(sectionOffset); } }
The function is called once when creating the application in the mounted()
function.
mounted() { this.calculateSectionOffsets(); }
Event Listener
We need some event listeners to intercept the scroll movement for desktop and swipe on mobile devices. We register these listeners in the mounted()
function.
window.addEventListener('DOMMouseScroll', this.handleMouseWheelDOM); // Mozilla Firefox window.addEventListener('mousewheel', this.handleMouseWheel, { passive: false }); // Other browsers window.addEventListener('touchstart', this.touchStart, { passive: false }); // mobile devices window.addEventListener('touchmove', this.touchMove, { passive: false }); // mobile devices
We also remove the event listeners when quitting the application in the destroyed()
function.
destroyed() { window.removeEventListener('mousewheel', this.handleMouseWheel, { passive: false }); // Other browsers window.removeEventListener('DOMMouseScroll', this.handleMouseWheelDOM); // Mozilla Firefox window.removeEventListener('touchstart', this.touchStart); // mobile devices window.removeEventListener('touchmove', this.touchMove); // mobile devices }
As you can see from the comments, there are different events for different browsers. For some browsers the parameter passive: false
is given. This parameter has to be inserted.
I tried a lot of scrolling until it worked smoothly. I could only get the result with this parameter. In addition, error messages were displayed in the browser console.
Scroll Function
This function is already called by our HTML links. Herewith we scroll to the section. The parameter id
is the Section ID. The variable inMove
makes sure that we have a small cooldown. This means that we can only scroll every 400ms (0.4s). With the parameter force
set to true
we can skip the cooldown.
scrollToSection(id, force = false) { if (this.inMove && !force) return false; this.activeSection = id; this.inMove = true; document.getElementsByTagName('section')[id].scrollIntoView({ behavior: 'smooth' }); setTimeout(() => { this.inMove = false; }, this.inMoveDelay); },
Recognize Scroll Direction
The handleMouseWheel
function is the event listener of the scroll event on desktop devices (mousewheel
& DOMMouseScroll
).
Via the variable wheelDelta
of the event we can see if the user scrolls up or down. Accordingly, our moveUp()
or moveDown()
function is called, which is created in the next step. At the end, e.preventDefault()
and return false;
abort the event.
handleMouseWheel: function(e) { if (e.wheelDelta < 30 && !this.inMove) { this.moveUp(); } else if (e.wheelDelta > 30 && !this.inMove) { this.moveDown(); } e.preventDefault(); return false; },
Scroll section up and down
These two functions are only responsible for scrolling up or down once. If we scroll down and the section is smaller than 0, we scroll to the last section. On the other hand, if the new section is larger than the number of sections, we scroll to the first section. 0 is the first section.
moveDown() { this.inMove = true; this.activeSection--; if (this.activeSection < 0) this.activeSection = this.offsets.length - 1; this.scrollToSection(this.activeSection, true); }, moveUp() { this.inMove = true; this.activeSection++; if (this.activeSection > this.offsets.length - 1) this.activeSection = 0; this.scrollToSection(this.activeSection, true); }
With this we have created an endless scroll. If you don’t want to do this, you simply can’t do a Swipe, so the user can only scroll in the other direction at the ends.
Detect Mobile Swipe
We have the touchstart
and touchmove
events on the move, through which we can see where the user is scrolling. When the user starts scrolling, touchStart()
is called. There we store the Y position. When the user then moves his finger on the display, touchMove()
is called.
We then compare these two values and see whether the user scrolls up or down. Accordingly, our moveUp()
or moveDown()
function, which we created earlier, is called.
touchStart(e) { e.preventDefault(); this.touchStartY = e.touches[0].clientY; }, touchMove(e) { if (this.inMove) return false; e.preventDefault(); const currentY = e.touches[0].clientY; if (this.touchStartY < currentY) { this.moveDown(); } else { this.moveUp(); } this.touchStartY = 0; return false; }
Conclusion
As you can see, you can add your own features or remove parts if you don’t like them. A Vue.js Fullpage Scroll is a very chic feature and when used properly you can create very nice results.
Did you know that you can use Vue.js without build tools, etc.? No, then check out my article on using Vue without Node or CLI.
What did you think of this post?
Inspired by your article, I made it as composable, to separate code from components – https://gist.github.com/gintsgints/96e6b18f8291d2264ff2984e6af5b2f6
Very cool, I will link your work when I revise this post! 🙂
Hello.How do I add the keyboard arrow keys event?
Hi, here’s a resource from vue how to add key modifiers: https://vuejs.org/v2/guide/events.html#Key-Modifiers
You can implement the key event and then call moveUp() or moveDown() function
Hope this helps!
Thank you
hi, nice tutorial!same code. but i have one problem. when i scroll make some error like this : Uncaught TypeError: Cannot read properties of undefined (reading ‘scrollIntoView’) at eval (App.vue?3dfd:117:1)how can i solve problem..?
Thanks! Check if your section tags exists. It seems that document.getElementsByTagName(‘section’)[id] is undefined. So you can log id and check if the element exists. Hope this helps!
I have problem with it. If i use touchpad of laptop and scroll . It is very difficult to control it. Please tell me how i can control touchpad . Thank you so much
I’m not sure, but doesn’t the touchpad use the same scroll function like a normal mouse? Actually it must work…what exactly is the bug?
I have the same problem, is there a solution? There is a big difference between scrolling with the mouse and scrolling with the laptop.
Hello, I have the same problem, is there a solution? There is a big difference between scrolling with the mouse and scrolling with the laptop.
Thank you for the tutorial. Amazing work
IS there a way not to be endless? just top to bottom and reverse
Thanks! Of course, you can adjust line 44 to: if(this.activeSection < 0) return false;
And line 52 to if(this.activeSection > this.offsets.length – 1) return false;
Hope this helps! 🙂
HI, is there an option / way to disable the function on mobile?
Of course. You can use parseFloat(getComputedStyle(document.querySelector(‘html’), null).width.replace(“px”, “”)) to get the with and than just use a simple if/else statement with your prefered width. Hope this helps 🙂
Hi,The Code is great, thanks for sharing,just one thing about the touch behavior. While the scrolling is working users are not able to make a normal click/touch on a link. I think is has to be with the “e.preventDefault()”. Once you comment that on touchStart() and touchMove() you can click/touch but the scrolling goes funny on mobile devices.Any hint how to solve it?
Is there a solution to the problem?
Thx so much!
But i used mounted(), instead created() . Because when I try refresh my site, script does`n work
Hallo erstmal,
danke für das Tutorial!
Leider tritt bei mir ständig ein Fehler auf, den ich mir nicht erklären kann:
Im einen Moment funktioniert alles problemlos, doch wenn ich auf der ersten Seite nach oben Scrolle, bekomme ich den Fehler: “Uncaught TypeError: Cannot read property ‘scrollIntoView’ …”
Er findet wohl keine Sections mehr, denn das Menü verschwindet und ich kann nicht mehr scrollen.
Vielen Dank und viele Grüße
JM
Das Problem konnte ich mittlerweile beheben. Es lag daran, dass das HTML wohl länger zum laden braucht/vor dem JS geladen wird. Dadurch kann das Script noch keine Sektionen finden.
Gelöst habe ich das ganze wie folgt:
setTimeout(() => {
let sections = document.getElementsByClassName("fullpage")
let length = sections.length;
console.log(length);
for(let i = 0; i < length; i++) {
let sectionOffset = sections[i].offsetTop;
this.offsets.push(sectionOffset);
}
}, 200);
Viele Grüße
JM
Oke, hatte Dir gerade schon geantwortet, habe erst jetzt Deine Antwort gesehen. Danke für die Rückmeldung! 🙂 Das vermerke ich beim nächsten Überarbeiten auf jeden Fall.
Hi Joshua,
lass Dir mal den Wert
this.activeSection
in der moveDown() Funktion ausgeben und schau ob dieser korrekt berechnet wurde. Die Fehlermeldung besagt ja, dass die übergeben Section nicht gefunden wurde. Vielleicht hilft Dir das weiter, ansonsten melde Dich gerne nochmal. 🙂Viele Grüße
Lorenz
Hey there would you mind stating which blog platform you’re using?
I’m looking to start my own blog in the near future but I’m having a tough time selecting between BlogEngine/Wordpress/B2evolution and Drupal.
The reason I ask is because your design seems different then most blogs and
I’m looking for something unique. P.S Sorry for being off-topic
but I had to ask!
Hey, I’m using WordPress as CMS. But I made the theme myself, so it’s completly custom. 🙂
There are a lot of good CMS solutions, take the CMS you prefer.
Greetings
LH
Thank you so much, that resolved my problem!
Gladly! 🙂
Hello.
First of all I’d like to thank you for the wonderful Fullpage Scroll. I used it on one of my projects.
Got one question though. Is there any chance to make scrolls to work a little bit slower? I mean when you scroll up and down the slides goes a bit too fast, how to configure that? I was unable to find a solution to that the only thing Ive found was behavior: ‘smooth’ property.
Thanks in advance for your answer.
Best regards. Rafał.
Hey, thank you!
I know the problem, but I have no solution for that. There is a way to do that in JavaScript, but this is not working with Vue.
If I found a solution, I’ll publish it in this article!
LH
Thank you for your anwer! I’ll try to fix. Hopefully i can find solution to that. I most definitely let you know 🙂
Got one more question again, is there any way to stop scroll after last slide? So it won’t go up again?
Best regards. Rafał.
Yes, of course. You can change the moveUp() and moveDown() function. Just change the if-statements. They are just there to check if you are at the last slide. 🙂
LH
HEy, nice tutorial, but I have found some issue with it, it does not work always with hook created, if it is inside external component, so use hook mounted to fix it, but still, if user refresh page which is on e.g green section, page will be rendered again on old position, but it wont recognize this, and it think it is on first instead of 4 section ;/
Thanks! Good hint for all who build the feature in your way.
LH
Is there a solution to the problem?
Not so far, maybe a timeout helps? I have not yet looked for it in detail…
Guten Tag
Erstmal finde ich dieses Tutorial grossartig! Hut ab!
Nun, ich wollte den pagescroll eigentlich gerade in meine Vue.js App implementieren, aber aus irgendeinem Grund hatte ich Probleme (nutze den vue cli, vielleicht liegt es an dem).
Hast du den Code schon einmal mit vue.js cli probiert?
Beste Grüsse
Lukas
Hi,
danke dir!
Ich habe es über die CLI noch nicht probiert, aber der Code muss dort auch funktionieren, hier sind keine Besonderheiten eingebaut, die dort nicht funktionieren sollten.
Was für ein Fehler wurde denn angezeigt bzw. was funktioniert nicht richtig?
Viele Grüße
LH