In this tutorial you will learn how to create a simple mouse wheel scroller. The following solution is very basic. It can be greatly improved, but it just an example of how fast it can be implemented or a good starting point to create your own slider. If you need a full-featured slider, use this awesome library by . fullpage.js 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. ⚓️ How I came up with this solution? 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. 🔨 Preparation 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 ▶️ HTML Boilerplate First, initiate the simple HTML5 project. For this tutorial, I suggest using this awesome boilerplate called . Select the and tune the following settings: initializr Classic H5BP 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 using the or : http-server npm yarn $ npm install -g http-server $ yarn global add http-server # or In your project root, run the server 🚀 $ 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 # -c-1 (disable cache) Go to in your browser and you should see the https://localhost:8080 Hello world! This is HTML5 Boilerplate. ✏️ Create the page content Open the and find the following line index.html Hello world! This is HTML5 Boilerplate. < > p </ > p Replace it with our sections: 1. Viewport height section 2. Long section 3. Short section 4. Viewport height section < = = > div id "section1" class "section" < > span </ > span </ > div < = = > div id "section2" class "section" < > span </ > span </ > div < = = > div id "section3" class "section" < > span </ > span </ > div < = = > div id "section4" class "section" < > span </ > span </ > div Now in find the block: css/main.css /* ========================================================================== Author's custom styles ========================================================================== */ In this block add the styles of our content { : white; : flex; : center; : center; : ; : ; : ; : relative; } { : ; : ; } { : ; : ; } { : ; : ; } { : ; : ; } /* We center the text, make the text color white and increase the font size */ .section color display align-items justify-content font-weight 800 font-size 120% font-weight 800 position /* 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: 🎇 Let's add some magic 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 class name, and define the event handler. .section wheel $sections = $( ); .addEventListener( , { }, { : }); // Collecting the sections var ".section" // Define wheel event handler document "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: $sections = $( ); .addEventListener( , { direction = event.deltaY; (direction > ) { } { } }, { : } ); // Collecting the sections var ".section" // Define wheel event handler document "wheel" ( ) function event // Get the mouse wheel spin direction var if 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. $sections = $( ); currentIndex = ; .addEventListener( , { direction = event.deltaY; (direction > ) { currentIndex++; $nextSection = $($sections[currentIndex]); } { currentIndex--; $previousSection = $($sections[currentIndex]); } }, { : } ); // Collecting the sections var ".section" // Variable to hold the current section index var 0 // Define wheel event handler document "wheel" ( ) function event // Get the mouse wheel spin direction var if 0 // Go to next // Increase the section pointer // Get the next section var else // Go to prev // Decrease the section pointer // Get the previous section var 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 $sections = $( ); currentIndex = ; .addEventListener( , { direction = event.deltaY; (direction > ) { (currentIndex + >= $sections.length) ; currentIndex++; $nextSection = $($sections[currentIndex]); } { (currentIndex - < ) ; currentIndex--; $previousSection = $($sections[currentIndex]); } }, { : } ); // Collecting the sections var ".section" // Variable to hold the current section index var 0 // Define wheel event handler document "wheel" ( ) function event // Get the mouse wheel spin direction var if 0 // If next index is greater than sections count, do nothing if 1 return // Go to next // Increase the section pointer // Get the next section var else // If previous index is negative, do nothing if 1 0 return // Go to prev // Decrease the section pointer // Get the previous section var 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. $sections = $( ); currentIndex = ; .addEventListener( , { direction = event.deltaY; (direction > ) { (currentIndex + >= $sections.length) ; currentIndex++; $nextSection = $($sections[currentIndex]); offsetTop = $nextSection.offset().top; event.preventDefault(); $( ).animate({ : offsetTop }, ); } { (currentIndex - < ) ; currentIndex--; $previousSection = $($sections[currentIndex]); offsetTop = $previousSection.offset().top; event.preventDefault(); $( ).animate({ : offsetTop }, ); } }, { : } ); // Collecting the sections var ".section" // Variable to hold the current section index var 0 // Define wheel event handler document "wheel" ( ) function event // Get the mouse wheel spin direction var if 0 // If next index is greater than sections count, do nothing if 1 return // Go to next // Increase the section pointer // Get the next section var // Get the next section offset var // Prevent the default mouse wheel behaviour // Animate scroll "html, body" scrollTop 1000 else // If previous index is negative, do nothing if 1 0 return // Go to prev // Decrease the section pointer // Get the previous section var // Get the previous section offset var // Prevent the default mouse wheel behaviour // Animate scroll "html, body" scrollTop 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 when animation starts, and - when animation finished. In the event handler, if we detected that animation is in progress, we just prevent the default mouse wheel behavior. true false $sections = $( ); currentIndex = ; isAnimating = ; stopAnimation = { setTimeout( { isAnimating = ; }, ); }; .addEventListener( , { (isAnimating) { event.preventDefault(); ; } direction = event.deltaY; (direction > ) { (currentIndex + >= $sections.length) ; currentIndex++; $nextSection = $($sections[currentIndex]); offsetTop = $nextSection.offset().top; event.preventDefault(); isAnimating = ; $( ).animate({ : offsetTop }, , stopAnimation); } { (currentIndex - < ) ; currentIndex--; $previousSection = $($sections[currentIndex]); offsetTop = $previousSection.offset().top; event.preventDefault(); isAnimating = ; $( ).animate({ : offsetTop }, , stopAnimation); } }, { : } ); // Collecting the sections var ".section" // Variable to hold the current section index var 0 // Variable to hold the animation state var false // Define the animation finish callback var ( ) function // We add the 300 ms timeout to debounce the mouse wheel event ( ) function // Set the animation state to false false 300 // Define wheel event handler document "wheel" ( ) function event // If animation is in progress if // Just prevent the default mouse wheel behaviour return // Get the mouse wheel spin direction var if 0 // If next index is greater than sections count, do nothing if 1 return // Go to next // Increase the section pointer // Get the next section var // Get the next section offset var // Prevent the default mouse wheel behaviour // Set the animation state to true true // Animate scroll "html, body" scrollTop 1000 else // If previous index is negative, do nothing if 1 0 return // Go to prev // Decrease the section pointer // Get the previous section var // Get the previous section offset var // Prevent the default mouse wheel behaviour // Set the animation state to true true // Animate scroll "html, body" scrollTop 1000 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 ( ). You can't scroll to the end of this section, because on the mouse wheel spin, section 3 ( ) comes in to view. 2. Long section 3. Short section 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 bottomIsReached = { rect = $elem[ ].getBoundingClientRect(); rect.bottom <= $( ).height(); }; topIsReached = { rect = $elem[ ].getBoundingClientRect(); rect.top >= ; }; // Function returns true if DOM element bottom is reached var ( ) function $elem var 0 return window // Function returns true if DOM element top is reached var ( ) function $elem var 0 return 0 In the handler add the logic which prevents the slide as mentioned above. $sections = $( ); currentIndex = ; isAnimating = ; stopAnimation = { setTimeout( { isAnimating = ; }, ); }; bottomIsReached = { rect = $elem[ ].getBoundingClientRect(); rect.bottom <= $( ).height(); }; topIsReached = { rect = $elem[ ].getBoundingClientRect(); rect.top >= ; }; .addEventListener( , { (isAnimating) { event.preventDefault(); ; } $currentSection = $($sections[currentIndex]); direction = event.deltaY; (direction > ) { (currentIndex + >= $sections.length) ; (!bottomIsReached($currentSection)) ; currentIndex++; $nextSection = $($sections[currentIndex]); offsetTop = $nextSection.offset().top; event.preventDefault(); isAnimating = ; $( ).animate({ : offsetTop }, , stopAnimation); } { (currentIndex - < ) ; (!topIsReached($currentSection)) ; currentIndex--; $previousSection = $($sections[currentIndex]); offsetTop = $previousSection.offset().top; event.preventDefault(); isAnimating = ; $( ).animate({ : offsetTop }, , stopAnimation); } }, { : } ); // Collecting the sections var ".section" // Variable to hold the current section index var 0 // Variable to hold the animation state var false // Define the animation finish callback var ( ) function // We add the 300 ms timeout to debounce the mouse wheel event ( ) function // Set the animation state to false false 300 // Function returns true if DOM element bottom is reached var ( ) function $elem var 0 return window // Function returns true if DOM element top is reached var ( ) function $elem var 0 return 0 // Define wheel event handler document "wheel" ( ) function event // If animation is in progress if // Just prevent the default mouse wheel behaviour return // Get the current section var // Get the mouse wheel spin direction var if 0 // If next index is greater than sections count, do nothing if 1 return // If bottom is not reached allow the default behaviour if return // Go to next // Increase the section pointer // Get the next section var // Get the next section offset var // Prevent the default mouse wheel behaviour // Set the animation state to true true // Animate scroll "html, body" scrollTop 1000 else // If previous index is negative, do nothing if 1 0 return // If top is not reached allow the default behaviour if return // Go to prev // Decrease the section pointer // Get the previous section var // Get the previous section offset var // Prevent the default mouse wheel behaviour // Set the animation state to true true // Animate scroll "html, body" scrollTop 1000 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. ✔️ You reached the bottom! 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. 🌟 Follow on 🌟 Connect on Twitter LinkedIn