+

+Goal

+ +

This package is designed to help dealing with the scroll event in the browser in the smoothest way possible.

+ +

Yes, web browsers already deliver ways to listen for the scroll event and they work really well. What scroll-proxy does is proxy the calls from you to the native scroll event and the other way around, doing some transformations in the process.

+ +

It applies some techniques already frequently used by web developers such as function throttling, animation toggling and a few others on scroll-related events. Delivering a small library one can just download and use.

+ +

The project's main focus is to help developers achieve great scroll performance in their apps while still being able to implement stuff like scroll-based animations, sticky headers and infinite scrolling.

+ +

In fact, the sticky header on this page is made in a few lines of code using scroll-proxy. Check your inspector!

+ +

+Install

+ +

The installation process is pretty straight-forward.

+ +

You can get it on npm:

+ +

npm install scroll-proxy --save

+ +

Or bower:

+ +

bower install scroll-proxy --save

+ +

If you are not into package management there is also the option of direct download, after unzipping the JS file will be in the dist folder.

+ +

Don't want to download at all? No problem. Just get it from one of the CDN providers.

+ +

+Usage

+ +

+How it works

+ +

One of the biggest concerns when developing scroll-proxy is its interoperability with other libs and frameworks.

+ +

So we decided not to change the native scroll handling. Instead, we create a wrapper that can handle everything the native can do and more.

+ +

This way we avoid breaking any other lib that may rely on the native scroll event.

+ +

The coolest part of the lib is the event handling, you can attach as many events you like but it will only attach one listener to the native scroll event in the browser. This practice is often called event delegation.

+ +

Please setup the lib and check for yourself.

+ +

+Setup

+ +

Setting up the library is simple and easy.

+ +

Once you've installed it and properly added it to your document via a script tag you just have to instantiate a new ScrollProxy object.

+ +
var myScroll = new ScrollProxy();
+ +

You just did it!

+ +

Notice that you didn't pass any arguments to the ScrollProxy constructor. By default, ScrollProxy will always attach to document.body. It means it will report actions when the user scrolls the page.

+ +

If you want to get updates when the user scrolls inside some specific HTMLElement you must pass it to the constructor:

+ +
var myDiv = document.querySelector('.scrollable');
+var myDivScroll = new ScrollProxy(myDiv);
+ +

+Note for CommonJS/browserify users

+ +

The CommonJS module version needs you to require ScrollProxy before using it like above.

+ +
var ScrollProxy = require("scroll-proxy");
+ +

+Registering scroll events

+ +

OK, ScrollProxy's set up. Now let's make some cool stuff.

+ +

The first thing you might want to do is to get updates whenever the user scrolls the page.

+ +

Usually, using just browsers APIs and vanilla JS, you could do that like this:

+ +
window.addEventListener('scroll', function() {
+  console.log("YAY, I'm scrolling!");
+}, false);
+ +

And that would work great.

+ +

But note that it bombs your console with a lot of logs. That's because each movement on the scroll area causes one scroll event to fire.

+ +

That could be fine for logging as it's pretty fast but imagine doing complex calculations or animating stuff based on scroll position. That would be really slow and certainly laggy.

+ +

+Here comes scroll-proxy to the rescue!

+ +

In scroll-proxy registering an event is as easy as calling the on function passing the name of the event and the callback function.

+ +
var s = new ScrollProxy() // It defaults to document.body, just as we want it
+s.on('scroll', function() {
+  console.log("YAY, I'm scrolling!");
+});
+ +

Now your scroll event belongs to ScrollProxy, it will automatically throttle the event calls with a 250ms delay by default.

+ +

That's great for most use cases but you can actually change it on the fly! Take a look:

+ +
s.setScrollDelay(100);
+ +

Now subsequent events are respecting a 100ms delay. Easy, huh?

+ +

You could also just opt to get just a ping when the scroll event happened instead of keep monitoring, the function you are looking for is once. It is like on but, as the name implies, only fires once.

+ +
s.once('scroll', function() {
+  console.log("I have just scrolled");
+})
+ +

Take your time to compare both vanilla and ScrollProxy implementations and see the difference. It certainly helps tame the frenzy scroll event calls.

+ +

Fell free to the behavior using your browser inspector right here in this page. We already have a ScrollProxy instance setup in window.myScroll.

+ +

+ScrollProxy's custom events

+ +

Registering the scroll event is not the only thing ScrollProxy can do, actually it has a bunch of other cool events you can listen with the built in on and once functions.

+ +

The full list of events you can rely on:

+ +
    +
  • scroll
  • +
  • offsetX
  • +
  • offsetY
  • +
  • top
  • +
  • bottom
  • +
  • left
  • +
  • right
  • +
  • visible
  • +
  • invisible
  • +
+ +

+Special events

+ +

Some events accept a third argument in the on and once functions. The presence of this third argument and it's value modify the event handling.

+ +

The offset events

+ +

Offset events are great when you want to check scroll in a specific direction, the next example shows that in a simple way. The circle starts in red and changes to green if you scroll vertically and blue if you scroll horizontally

+ +

Try it out! Scroll the area below.

+ +
+
#offset-example1
+
+
+
+
+
+
+ +

Code:

+ +
var s = new ScrollProxy(document.getElementById('offset-example1'));
+s.on('offsetX', function(){
+  changeColor('blue');
+});
+s.on('offsetY', function(){
+  changeColor('green');
+});
+ +

The offsetX and offsetY accept a third argument indicating a offset for scroll-proxy it to consider when firing the event. Take a look as the red bar becomes green when it fills the entire scroll area.

+ +
+
#offset-example2
+
+
+
+
+ +

That happened because we are passing the third argument with value 300, so ScrollProxy will emit the offset event when scroll past 300 pixels.

+ +

Code:

+ +
var s = new ScrollProxy(document.getElementById('offset-example2'));
+s.once('offsetY', function(){
+  changeColor('green');
+}, 300);
+ +

Note the presence of the optional third argument on the second example. It indicates an offset to respect. The same logic applies to the offsetX event as well.

+ +

The top, bottom, left and right events

+ +

These kind of events are usually used to determine if the user reached some of the borders of the scroll area. The third argument is useful here when you want to tell scroll-proxy to fire the event earlier than normal.

+ +

Let's say you want to implement infinite scrolling. You could just load new stuff when the user reaches the bottom of the scroll area, right? Yes, this would work, but wouldn't it be nicer if you could actually load the content before the user notices any loading?

+ +

For these types of use case the third argument comes handy.

+ +

Sometimes you want to actually get notified when the scroll area reaches the bottom.

+ +

See the text color turn green when you reach the bottom of the scroll area below in the next example:

+ +
+
#bound-example1
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lectus ex, maximus sit amet ante mollis, fermentum blandit nulla. Nunc vehicula, nunc at placerat tempus, metus ante lacinia arcu, eu accumsan risus est id lectus. Phasellus semper orci in dolor finibus, quis molestie massa gravida. Donec sollicitudin nulla at orci iaculis, ac pretium ipsum rutrum. Ut accumsan, lacus tristique viverra commodo, velit nisl viverra sem, in pharetra risus massa sit amet nunc. Nam rutrum elit sed ex cursus suscipit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum rutrum, nibh id viverra convallis, ipsum nisi condimentum mauris, vel imperdiet quam nunc efficitur nisl. Cras elementum odio lorem, eget semper ligula malesuada id. In hac habitasse platea dictumst.Duis venenatis massa quis sapien porta convallis. Aenean facilisis ante vitae nibh laoreet, id vestibulum lacus tristique. Curabitur consectetur nunc nec dui fringilla posuere. Vestibulum nec convallis elit. Fusce leo sapien, sodales ac mauris et, imperdiet iaculis tellus. Nullam sagittis placerat condimentum. Duis ultricies augue id nibh rutrum, vel auctor diam vulputate. Aliquam scelerisque ex turpis, id fermentum urna condimentum id. Proin semper, lorem sed scelerisque faucibus, felis velit consequat metus, malesuada ultrices tellus est eget leo. Sed in velit id orci condimentum mollis nec eu purus. Integer dignissim lorem purus, id porta nibh blandit id. Aliquam ac diam finibus, lacinia leo sit amet, rutrum nibh.

+
+
+
+ +

Code:

+ +
var s = new ScrollProxy(document.getElementById('bound-example1'));
+s.once('bottom', function(){
+  changeColor('green');
+});
+ +

And on other times you may want to get notified earlier. You can do that by passing the third argument.

+ +

The next example looks like the previous one but this time the text turns yellow when approaching the end. Then, when finally hitting it, it turns green:

+ +
+
#bound-example2
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lectus ex, maximus sit amet ante mollis, fermentum blandit nulla. Nunc vehicula, nunc at placerat tempus, metus ante lacinia arcu, eu accumsan risus est id lectus. Phasellus semper orci in dolor finibus, quis molestie massa gravida. Donec sollicitudin nulla at orci iaculis, ac pretium ipsum rutrum. Ut accumsan, lacus tristique viverra commodo, velit nisl viverra sem, in pharetra risus massa sit amet nunc. Nam rutrum elit sed ex cursus suscipit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum rutrum, nibh id viverra convallis, ipsum nisi condimentum mauris, vel imperdiet quam nunc efficitur nisl. Cras elementum odio lorem, eget semper ligula malesuada id. In hac habitasse platea dictumst.Duis venenatis massa quis sapien porta convallis. Aenean facilisis ante vitae nibh laoreet, id vestibulum lacus tristique. Curabitur consectetur nunc nec dui fringilla posuere. Vestibulum nec convallis elit. Fusce leo sapien, sodales ac mauris et, imperdiet iaculis tellus. Nullam sagittis placerat condimentum. Duis ultricies augue id nibh rutrum, vel auctor diam vulputate. Aliquam scelerisque ex turpis, id fermentum urna condimentum id. Proin semper, lorem sed scelerisque faucibus, felis velit consequat metus, malesuada ultrices tellus est eget leo. Sed in velit id orci condimentum mollis nec eu purus. Integer dignissim lorem purus, id porta nibh blandit id. Aliquam ac diam finibus, lacinia leo sit amet, rutrum nibh.

+
+
+
+ +

Code:

+ +
var s = new ScrollProxy(document.getElementById('bound-example2'));
+s.once('bottom', function(){
+  changeColor('yellow');
+}, 250);
+s.once('bottom', function(){
+  changeColor('green');
+});
+ +

The visible and invisible events

+ +

These are very special events that have limited use cases but sometimes are very welcome.

+ +

Both events will only work if the third argument is given. Yes, the third argument is not optional when using either visible or invisible events.

+ +

That's because the events need to check if the element passed in the third argument is visible in the scroll area.

+ +

There's another catch: the element to be checked must be a direct child of the scroll area scroll-proxy is attached to.

+ +

Example of detecting changes in objects visibility:

+ +
+
#visibility-example
+
+
+
+
+
+ +
+
+ +

Code:

+ +
var s = new ScrollProxy(document.getElementById('visibility-example'));
+s.on('visible', function(){
+  report('SQUARE VISIBLE :-)');
+}, square);
+s.on('invisible', function(){
+  report('SQUARE NOT VISIBLE :-(');
+}, square);
+ +

+Getting metadata

+ +

scroll-proxy also delivers current information about the scroll area.

+ +

You can fetch info about the x and positions, the width and height of the scroll element and also the width and height of the scroll area.

+ +
// Consider a window of 800x600 with a 2000x2000 div inside
+var s = new ScrollProxy();
+console.log(s.x); // Will log 0
+console.log(s.y); // Will log 0
+console.log(s.width); // Will log 800
+console.log(s.height); // Will log 600
+console.log(s.scrollWidth); // Will log 2000
+console.log(s.scrollHeight); // Will log 2000
+ +

All the info you see above is updated realtime if you registered at least one event with on. In fact if you want to use these kind of info inside the event callback you could do like this:

+ +

Example:

+ +
+
#metadata-example
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lectus ex, maximus sit amet ante mollis, fermentum blandit nulla. Nunc vehicula, nunc at placerat tempus, metus ante lacinia arcu, eu accumsan risus est id lectus. Phasellus semper orci in dolor finibus, quis molestie massa gravida. Donec sollicitudin nulla at orci iaculis, ac pretium ipsum rutrum. Ut accumsan, lacus tristique viverra commodo, velit nisl viverra sem, in pharetra risus massa sit amet nunc. Nam rutrum elit sed ex cursus suscipit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum rutrum, nibh id viverra convallis, ipsum nisi condimentum mauris, vel imperdiet quam nunc efficitur nisl. Cras elementum odio lorem, eget semper ligula malesuada id. In hac habitasse platea dictumst.Duis venenatis massa quis sapien porta convallis. Aenean facilisis ante vitae nibh laoreet, id vestibulum lacus tristique. Curabitur consectetur nunc nec dui fringilla posuere. Vestibulum nec convallis elit. Fusce leo sapien, sodales ac mauris et, imperdiet iaculis tellus. Nullam sagittis placerat condimentum. Duis ultricies augue id nibh rutrum, vel auctor diam vulputate. Aliquam scelerisque ex turpis, id fermentum urna condimentum id. Proin semper, lorem sed scelerisque faucibus, felis velit consequat metus, malesuada ultrices tellus est eget leo. Sed in velit id orci condimentum mollis nec eu purus. Integer dignissim lorem purus, id porta nibh blandit id. Aliquam ac diam finibus, lacinia leo sit amet, rutrum nibh.

+
+
+
+ Output Log: + +
+
+ +

Code:

+ +
var s = new ScrollProxy(document.getElementById('metadata-example'));
+s.on('scroll', function(){
+  var output = 'X: ' + this.x + '<br>';
+  output +=    'Y: ' + this.y + '<br>';
+  output +=    'Element Width: ' + this.width + '<br>';
+  output +=    'Element Height: ' + this.height + '<br>';
+  output +=    'Scroll Width: ' + this.scrollWidth + '<br>';
+  output +=    'Scroll Height: ' + this.scrollHeight;
+  report(output);
+});
+ +

+Getting rid of unused events

+ +

If you want to remove a event listener created with on or cancel an once event before it is even fired you can just use off.

+ +
var s = new ScrollProxy();
+s.on('scroll', function() {
+  console.log('YAY!');
+});
+
+s.off('scroll'); // No more YAY! logging
+ +

+Unregistering the scroll-proxy instances

+ +

When you don't want an instance of scroll-proxy around anymore you should call unregister before losing the variable pointer. That way you assure the memory is clean and the events are released.

+ +
var s = new ScrollProxy();
+s.on('scroll', function() {
+  console.log('Scrolling');
+});
+
+// Unregistering. Stop event reports and clean stuff
+s.unregister(); // Stopped logging
+
+delete s; // Clear pointer, everything OK
+ +

The register function can recover unregistered instances and restore all the event handlers.

+ +
var s = new ScrollProxy();
+s.on('scroll', function() {
+  console.log('Scrolling');
+});
+
+// Unregistering stop event reports
+s.unregister(); // Stopped logging
+
+s.register(); // Logging again on scroll
+ +

Tip! If you already lost the variable pointing to the scroll-proxy instance you can just call ScrollProxy.clean() and get rid of all the attachments.

+ +
var s = new ScrollProxy(); // Attaching ScrollProxy to document.body
+
+// Getting scroll events
+s.on('scroll', function() {
+  console.log('Scrolling...');
+});
+
+delete s; // Remove s reference
+
+// Still getting 'Scrolling...' on scroll, because s.unregister() wasn't called
+
+// Can't s.unregister() now because it's undefined :(
+
+// So clean everything with ScrollProxy.clean()! :)
+ScrollProxy.clean();
+
+// Now it will stop logging and everything is fine again
+ +

+Dealing with animations

+ +

scroll-proxy can be a great partner in your quest for 60FPS animations!

+ +

The first thing you need to know is that by using the custom scroll event you are already helping your websites performance. As you read earlier it throttles the event firing and all you need to do is select a proper delay using setScrollDelay.

+ +

But, that's not all! There is another trick we built in the library: animation toggling. Yes, if you have a long page with a lot of hover animations you should be aware that scrolling that page may result in a janky experience.

+ +

To solve that you'll want to use the disableHoverOnScroll method. It will disable all hover animations inside the scroll area when the user is scrolling, resulting in a much better experience.

+ +
var s = new ScrollProxy();
+s.disableHoverOnScroll();
+ +

That's it! Don't worry, all the animations will come back to life once the user stops scrolling. It's all possible thanks to the pointer-events spec.

+ +

If you need to enable animations on scroll again for some reason you can do that:

+ +
s.enableHoverOnScroll();
+ +

Notice that it applies a little delay between the scroll stop and the animation enabling, if you want to change that delay use the setHoverDelay method.

+ +
// From now on, animations will resume after a 100ms delay after the scroll stop
+s.setHoverDelay(100);
+ +

+Demo

+ +

Try pressing the following button, it will toggle the hover animations on this very page. Then try scrolling over buttons to see if they react or not.

+ + + +

+Other great functionality

+ +

There are some other great features in the lib, check the full documentation for more information.

+ +

+Browser support

+ +

This library works with most modern browsers. Some features like the animation toggling require support for the css pointer-events spec.

+ +

Theoretical support should include the following browsers:

+ +