If you need a full-featured slider, use this awesome library fullpage.js by Alvaro Trigo.
There is a demo of this tutorial: epranka.github.io/sections-slider
Also, this tutorial includes how to prepare a simple HTML project and run the live server.
Before several days, the client asks me to add a mouse wheel scroller to his website. I immediately thought about fullpage.js implementation. But the client website layout was "strictly" coded and he did not want to invest in changing it. So I had to come up with some dirty and fast solution without changing the entire layout. I warned the client that it is not a perfect solution, that he was fine with that.
If you have the already initiated the HTML5 project with jQuery, or have your own solution to create the live server of the simple HTML5 project, skip this step ⏩
If you want to follow this tutorial for the beginning, here we go ▶️
First, initiate the simple HTML5 project. For this tutorial, I suggest using this awesome boilerplate called initializr. Select the Classic H5BP and tune the following settings:
After the download, extract the archive. You should have the following tree in your project root:
.
├── css
│ ├── main.css
│ ├── normalize.css
│ └── normalize.min.css
├── img
├── js
│ ├── vendor
│ │ ├── jquery-1.11.2.min.js
│ │ └── modernizr-2.8.3.min.js
│ └── main.js
└── index.html
Live server
Now it's time to make your HTML project live ⏰
Install the http-server using the npm or yarn:
$ npm install -g http-server
# or
$ yarn global add http-server
In your project root, run the server 🚀
# -c-1 (disable cache)
$ http-server -c-1
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
http://192.168.8.10:8080
http://192.168.250.1:8080
Hit CTRL-C to stop the server
Go to https://localhost:8080 in your browser and you should see the Hello world! This is HTML5 Boilerplate.
Open the index.html and find the following line
<p>Hello world! This is HTML5 Boilerplate.</p>
Replace it with our sections:
<div id="section1" class="section">
<span>1. Viewport height section</span>
</div>
<div id="section2" class="section">
<span>2. Long section</span>
</div>
<div id="section3" class="section">
<span>3. Short section</span>
</div>
<div id="section4" class="section">
<span>4. Viewport height section</span>
</div>
Now in css/main.css find the block:
/* ==========================================================================
Author's custom styles
========================================================================== */
In this block add the styles of our content
/* We center the text, make the text color
white and increase the font size */
.section {
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 120%;
font-weight: 800;
position: relative;
}
/* The height of the first section
will be equal to the viewport height */
#section1 {
height: 100vh;
background: #6699cc;
}
/* The height of the second section
will be 150% of the viewport height */
#section2 {
height: 150vh;
background: #ff8c42;
}
/* 60% height */
#section3 {
height: 60vh;
background: #ff3c38;
}
/* 100% (equal again) */
#section4 {
height: 100vh;
background: #a23e48;
}
Now in the browser, you should see the colorful slides:
All the magic goes to the js/main.js.
The basic idea is to collect all the sections and animate the scroll between their offsets on the mouse wheel event. So first, using the JQuery we collect all sections by .section class name, and define the wheel event handler.
// Collecting the sections
var $sections = $(".section");
// Define wheel event handler
document.addEventListener("wheel", function(event) {
}, { passive: false });
// We set passive to false because in the handler we need to prevent the default mouse wheel behavior
In the handler determine the scroll direction:
// Collecting the sections
var $sections = $(".section");
// Define wheel event handler
document.addEventListener(
"wheel",
function(event) {
// Get the mouse wheel spin direction
var direction = event.deltaY;
if (direction > 0) {
// Go to next
} else {
// Go to previous
}
},
{ passive: false }
);
// We set passive to false, because in the handler we need to prevent the default mouse wheel behavior
In the following code, we define the variable which holds the current section index and in the handler, we get the next or previous section, depending on the scroll.
// Collecting the sections
var $sections = $(".section");
// Variable to hold the current section index
var currentIndex = 0;
// Define wheel event handler
document.addEventListener(
"wheel",
function(event) {
// Get the mouse wheel spin direction
var direction = event.deltaY;
if (direction > 0) {
// Go to next
// Increase the section pointer
currentIndex++;
// Get the next section
var $nextSection = $($sections[currentIndex]);
} else {
// Go to prev
// Decrease the section pointer
currentIndex--;
// Get the previous section
var $previousSection = $($sections[currentIndex]);
}
},
{ passive: false }
);
// We set passive to false, because in the handler we need to prevent the default mouse wheel behavior
Now we can select the section by the mouse wheel. But there is a problem. If we spin the mouse wheel too more we will get the undefined section because the pointer will be higher than the sections count. And if we spin the mouse wheel backward when we are in the first section we will get the negative pointer, which leads to the same problem: undefined section.
So we need to add the guards
// Collecting the sections
var $sections = $(".section");
// Variable to hold the current section index
var currentIndex = 0;
// Define wheel event handler
document.addEventListener(
"wheel",
function(event) {
// Get the mouse wheel spin direction
var direction = event.deltaY;
if (direction > 0) {
// If next index is greater than sections count, do nothing
if (currentIndex + 1 >= $sections.length) return;
// Go to next
// Increase the section pointer
currentIndex++;
// Get the next section
var $nextSection = $($sections[currentIndex]);
} else {
// If previous index is negative, do nothing
if (currentIndex - 1 < 0) return;
// Go to prev
// Decrease the section pointer
currentIndex--;
// Get the previous section
var $previousSection = $($sections[currentIndex]);
}
},
{ passive: false }
);
// We set passive to false, because in the handler we need to prevent the default mouse wheel behavior
Now we are safe. Just get the offset of the next or previous section and animate scroll to it.
// Collecting the sections
var $sections = $(".section");
// Variable to hold the current section index
var currentIndex = 0;
// Define wheel event handler
document.addEventListener(
"wheel",
function(event) {
// Get the mouse wheel spin direction
var direction = event.deltaY;
if (direction > 0) {
// If next index is greater than sections count, do nothing
if (currentIndex + 1 >= $sections.length) return;
// Go to next
// Increase the section pointer
currentIndex++;
// Get the next section
var $nextSection = $($sections[currentIndex]);
// Get the next section offset
var offsetTop = $nextSection.offset().top;
// Prevent the default mouse wheel behaviour
event.preventDefault();
// Animate scroll
$("html, body").animate({ scrollTop: offsetTop }, 1000);
} else {
// If previous index is negative, do nothing
if (currentIndex - 1 < 0) return;
// Go to prev
// Decrease the section pointer
currentIndex--;
// Get the previous section
var $previousSection = $($sections[currentIndex]);
// Get the previous section offset
var offsetTop = $previousSection.offset().top;
// Prevent the default mouse wheel behaviour
event.preventDefault();
// Animate scroll
$("html, body").animate({ scrollTop: offsetTop }, 1000);
}
},
{ passive: false }
);
// We set passive to false, because in the handler we need to prevent the default mouse wheel behavior
Tada! 🎉 Now we have the working mouse wheel slider. Check it out in your browser.
Okey... I know... It has the problem again... Our slider struggles if we spin the mouse wheel too fast. But why? When you spin the mouse wheel too fast, it starts several animations before the first animation finished. So we need to skip any mouse wheel events while animating the first one.
Define the variable which holds the state of the animation. Set variable to true when animation starts, and - false when animation finished. In the event handler, if we detected that animation is in progress, we just prevent the default mouse wheel behavior.
// Collecting the sections
var $sections = $(".section");
// Variable to hold the current section index
var currentIndex = 0;
// Variable to hold the animation state
var isAnimating = false;
// Define the animation finish callback
var stopAnimation = function() {
// We add the 300 ms timeout to debounce the mouse wheel event
setTimeout(function() {
// Set the animation state to false
isAnimating = false;
}, 300);
};
// Define wheel event handler
document.addEventListener(
"wheel",
function(event) {
// If animation is in progress
if (isAnimating) {
// Just prevent the default mouse wheel behaviour
event.preventDefault();
return;
}
// Get the mouse wheel spin direction
var direction = event.deltaY;
if (direction > 0) {
// If next index is greater than sections count, do nothing
if (currentIndex + 1 >= $sections.length) return;
// Go to next
// Increase the section pointer
currentIndex++;
// Get the next section
var $nextSection = $($sections[currentIndex]);
// Get the next section offset
var offsetTop = $nextSection.offset().top;
// Prevent the default mouse wheel behaviour
event.preventDefault();
// Set the animation state to true
isAnimating = true;
// Animate scroll
$("html, body").animate({ scrollTop: offsetTop }, 1000, stopAnimation);
} else {
// If previous index is negative, do nothing
if (currentIndex - 1 < 0) return;
// Go to prev
// Decrease the section pointer
currentIndex--;
// Get the previous section
var $previousSection = $($sections[currentIndex]);
// Get the previous section offset
var offsetTop = $previousSection.offset().top;
// Prevent the default mouse wheel behaviour
event.preventDefault();
// Set the animation state to true
isAnimating = true;
// Animate scroll
$("html, body").animate({ scrollTop: offsetTop }, 1000, stopAnimation);
}
},
{ passive: false }
);
// We set passive to false, because in the handler we need to prevent the default mouse wheel behavior
I could say that is done. But I don't wanna lie. If you check our slider in the browser you will see that there no struggle anymore. But we have the last thing to do. Look at the second section (2. Long section). You can't scroll to the end of this section, because on the mouse wheel spin, section 3 (3. Short section) comes in to view.
To fix this, we should prevent the slide to the next section if we don't reach the current section bottom and vice versa, we should prevent the slide to the previous section if we don't reach the current section top.
Define the two functions
// Function returns true if DOM element bottom is reached
var bottomIsReached = function($elem) {
var rect = $elem[0].getBoundingClientRect();
return rect.bottom <= $(window).height();
};
// Function returns true if DOM element top is reached
var topIsReached = function($elem) {
var rect = $elem[0].getBoundingClientRect();
return rect.top >= 0;
};
In the handler add the logic which prevents the slide as mentioned above.
// Collecting the sections
var $sections = $(".section");
// Variable to hold the current section index
var currentIndex = 0;
// Variable to hold the animation state
var isAnimating = false;
// Define the animation finish callback
var stopAnimation = function() {
// We add the 300 ms timeout to debounce the mouse wheel event
setTimeout(function() {
// Set the animation state to false
isAnimating = false;
}, 300);
};
// Function returns true if DOM element bottom is reached
var bottomIsReached = function($elem) {
var rect = $elem[0].getBoundingClientRect();
return rect.bottom <= $(window).height();
};
// Function returns true if DOM element top is reached
var topIsReached = function($elem) {
var rect = $elem[0].getBoundingClientRect();
return rect.top >= 0;
};
// Define wheel event handler
document.addEventListener(
"wheel",
function(event) {
// If animation is in progress
if (isAnimating) {
// Just prevent the default mouse wheel behaviour
event.preventDefault();
return;
}
// Get the current section
var $currentSection = $($sections[currentIndex]);
// Get the mouse wheel spin direction
var direction = event.deltaY;
if (direction > 0) {
// If next index is greater than sections count, do nothing
if (currentIndex + 1 >= $sections.length) return;
// If bottom is not reached allow the default behaviour
if (!bottomIsReached($currentSection)) return;
// Go to next
// Increase the section pointer
currentIndex++;
// Get the next section
var $nextSection = $($sections[currentIndex]);
// Get the next section offset
var offsetTop = $nextSection.offset().top;
// Prevent the default mouse wheel behaviour
event.preventDefault();
// Set the animation state to true
isAnimating = true;
// Animate scroll
$("html, body").animate({ scrollTop: offsetTop }, 1000, stopAnimation);
} else {
// If previous index is negative, do nothing
if (currentIndex - 1 < 0) return;
// If top is not reached allow the default behaviour
if (!topIsReached($currentSection)) return;
// Go to prev
// Decrease the section pointer
currentIndex--;
// Get the previous section
var $previousSection = $($sections[currentIndex]);
// Get the previous section offset
var offsetTop = $previousSection.offset().top;
// Prevent the default mouse wheel behaviour
event.preventDefault();
// Set the animation state to true
isAnimating = true;
// Animate scroll
$("html, body").animate({ scrollTop: offsetTop }, 1000, stopAnimation);
}
},
{ passive: false }
);
// We set passive to false, because in the handler we need to prevent the default mouse wheel behavior
Check the result in your browser.
It is far from perfect, but I can say that is done for this tutorial. And the idea is explained. I think you are strong enough to improve this to the perfect 😉
You can check the full source code in my GitHub repository epranka/sections-slider
Thanks for reading this. I hope it was helpful to you. Feedback and questions are appreciated.