Hi! This is future Bartek. I came from March 2017 to tell you that using Virtual Scroll can be a good idea for certain things like WebGL or Canvas interactive pieces and experiments. But it is never a good idea to use it on a website as a replacement of the default browser scroll. Trust me, I learned this the hard way. With this in mind – please continue reading :)

Parallax scrolling websites may not be the cool new thing anymore, but having the content react to the scroll in some fancy way has become a popular interaction pattern. When used in moderation, it can enhance the user experience and take the interface of a website to the next level. There are many ways to achieve scrolling effects, but the trick is always to make it as smooth as possible. Below I describe a technique I use to achieve scrolling effects on my projects.

When I started playing with different parallax-type effects, I quickly realized that the browser built-in scroll function, window.onscroll, is not very helpful. On a mobile device this event is fired only once after the scrolling action stops, so no animation is possible. On desktop it works a bit better, but there are other limitations. My biggest concern tough, was that I couldn’t get the fine level of control over the animation and easing I wanted.

I figured that a good approach would be to disable the browser scroll entirely and move the content using CSS transforms based on values read from an event listener. On desktop, I would listen to the mouse wheel event; on a touch screen, to the touch events. I named this pattern the VirtualScroll.

The first step is to disable the browser built-in scroll. To achieve this, just set your CSS on the body element to:

body { overflow:hidden; }

This will lock the content and prevent browser from scrolling. On touch screen devices, you need to do one more thing: prevent the touchmove event from doing its job of scrolling the site. This is how it’s done:

Next, you need to create a section of the HTML document that will be animated using VirtualScroll. Of course there are tons of ways of doing it, starting from a simple declarative approach and ending with apps that use AJAX to load and populate the DOM. Whichever way you do it, you should end up with a div or section tag holding all your scrollable content.

Now that the content is ready, we need to grab the height of the element holding it. To do this, there is actually a simple method that seems to work in all major browsers. Assuming that you used a section tag to host your content, it goes like this:

This technique has some issues when CSS margins are used. The element needs to be positioned absolutely in order for it to take margins into account when returning the height. It’s one of those weird, hard to explain features of CSS I guess… Anyway, what you need to do is simply:

section {
position: absolute;
}

The next step is to listen to the scroll events in Javascript. This is a bit complex, because different browsers treat mouse wheel events in a different way and also because we want to support several different modes of interaction – mouse, touch, keyboard. Here is the code I use. It deals with browser differences when it comes to the mouse wheel events, has listeners for touch events and also for keyboard events, so you can scroll the content using the cursor keys. Using it is very simple and it’s 100% dependency free. Just include VirtualScroll.js in your HTML document and use it this way:

VirtualScroll.on(function(e) {
// e is an object that holds scroll values, including:
e.deltaY; //
The callback is fired whenever the user uses his touch pad, mouse wheel, taps and drags the touch screen or presses a cursor key. Note that you should not use this function to move your content. Use this function only to accumulate the delta values and clamp them so that your scrolling content doesn't go offscreen. The animation is done elsewhere (see below).

For simplicity sake, let's assume you just want to scroll your content vertically. Here's how you can track the value you need:

The clamping of the targetY values above may require some explanation. Our scrolling animation will work by moving the HTML element inside the browser window using CSS transforms. To scroll down, we need to move the element up, above the top border of the browser window. So, in other words, we need to translate it on the Y axis in the negative direction. The furthest it should move is the height of the content itself minus the height of the window. This way the scroll will stop just when the bottom of the content reaches the bottom of the screen. On the other end, we should stop it at 0, because otherwise the top of the content will scroll below the top of the window.

Clamping the value is not mandatory however. If you want to use the VirtualScroll values in a different way, you can get creative with it. For example, you can do a scrolling pattern that repeats the content infinitely or infinitely loads more content (infinite scroll). You can do try a radial scrolling where things turn in round or do whatever else you want!

The CSS translation is done inside another function, one that runs at every frame, ideally 60 times per second. The best way to make a loop like this is to use requestAnimationFrame:

What happens here is that, the run function is executed on every frame thanks to the call to requestAnimationFrame at the beginning. Every time it is called, it uses a simple equation to move the value of currentY closer to the to targetY value.

The amount of easing depends on the value of the ease variable. I find values between 0.1 and 0.2 to give the best results. As the value gets closer to 1, the easing becomes less pronounced. Very small values on the other hand, like 0.001, will give a lot of easing and might make the site feel unresponsive. Experiment to see what you like best.

Notice the initial call to run() in the last line. Don't forget that, otherwise nothing will happen (I tend to make this mistake a lot... :)

The CSS transform does simply move the element along the Y axis. It also adds a 0 translation on the Z axis to make sure we get the GPU accelerated performance.

If everything worked fine, your content should now scroll smoothly at 60FPS across the screen. In fact, in many cases, scrolling like this gives smoother results than the default browser scroll. It also works nicely on mobile.

Here are 2 examples to show this code in action: one features moving a single element, the other one adds some parallax by moving several elements, each at a different pace. Look in to the source code of those examples for details and some additional comments.

Of course this example merely scratches the surface of what you can do. I move the content using a CSS translate, but you can tie the values returned by the VirtualScroll callback to anything. You can make an animation that scales or rotates elements on scroll. You can add some bouncy or elastic effects to the animation. You can even use the VirtualScroll to move through space 3d in a WebGL or CSS3D animation or to scrub through a video or image sequence.

I used this technique on several projects in the past, including my own portfolio, the Tool of North America site we built last year and some other parallax scrolling sites we did at Tool. The VirtualScroll script is part of a small framework I use for all my productions. Feel free to browse the docs and see if you like it.

Note. On the 'work' section of the Tool site we used horizontal scrolling. Horizontal scrolling is tricky, because some users on laptops and desktop computers have mouse wheels that only allow for vertical scrolling with the wheel. At first I wanted to track the vertical scrolling (i.e. e.deltaY) and use it to translate the content on the X axis instead of Y. However this doesn't work very well on mobile, where it's more natural to tap and drag in the horizontal direction if this is how the content moves. So the actual code detects the type of interaction used and tracks either the e.deltaX or e.deltaY depending on the situation. It's always good to think through all interactions like that!

I hope you find this technique useful. If you have any questions, post a comment below. If you find issues with VirtualScroll.js, please report them in the issues section on Github. Thanks!

Aaron Buchanan commented on August 20th, 2014

Nice, I will look forward to playing around with this technique!

Jul commented on September 9th, 2014

For the horizontal scrolling you mentioned at the end of the article, what technique do you use to determine whether to use deltaX or deltaY?

Neat technique Bartek, thanks for sharing! I don’t like the fact it hides the native scrollbar though. I guess you could keep overflow: auto and then just prevent the scroll event when the OSX browser tries to overshoot the scrollTop to the negative value? Or? :-)

@Jul in this case I’m checking if the screen is a touch screen or not. This can be as easy as:

var isTouch = 'ontouchstart' in document;

paul commented on October 23rd, 2014

hi. i really love this! i was just wondering how you would go about utilising other effects on scroll? i notice this site uses your script but it also fades out the logo when scrolling down and it fades back in when you scroll to the top:http://velvethammer.net
how would you do this? sorry but i’m new to JS.

I’m trying to simulate an anchor, how do I set a value for VirtualScroll currentY? so that it can automatically scroll to the point?

Thanks

Baptiste commented on January 6th, 2015

@Ricardo;

just set currentY a new value like this;

targetY = 200;

it will automatically scroll to this value

barto commented on February 14th, 2015

Hello, i m trying to integrate the virtual scroll into my website but ( im new in js )
so my problem is that i want my home page at 100% of the browser and i tried already many thing but doesn’t work
any ideas?

Quick question: (and I might be being stupid here) – But how did you go about setting up the “fake” scrollbar? Are there any other tutorials / examples that I might be able to break down easily in order to recreate.

Any advice / suggestion is very much appreciated.

Thanks!

Josh commented on November 19th, 2015

I’m with @Micheal, I’m still trying to find out how to update the page height when the window resizes.

And also have a consistent positive targetY, when console logging the sectionHeight variable I am sometimes getting negative integers which, well… doesn’t allow the page to scroll..

Hello there !
Just an update about the anchor problem (I know it was a long time ago) !
I made anchors in one of my website and it didn’t work well when the currentY wasn’t equal to 0… so I just add the currentY to the new targetY and it worked well !
targetY = myValue + currentY

Thanks for that awesome technique Bartek !

Paul Fitzgerald commented on September 15th, 2016

This is broken as far as i can tell as `scrollHeight` is not defined. I have tried to replace this with `sectionHeight` but this doesnt fix the issue either.

Hi Bartek! Thank you for writing this up. I would love to know what kind of problems you ran into. Can you share them with us? Thanks!

Post a comment

everyday3d is a blog run by Bartek Drozdz about web development with a focus on realtime 3d graphics. It's been around since 2007. First, it focused on Flash (remember?) then moved over to Unity3d, WebGL and eventually - Virtual Reality and 360°.

I hope you enjoy the articles and demos posted here. Most of what I write about, I thaught myself from good books. Here's a few I would recommend if you want to deepen your knowledge of creative coding, math and realtime graphics.