Parallax scrolling websites have become very popular recently. Some of the effects are pretty striking and create a very engaging user experience. I’ve been looking into parallax scrolling for something I’m designing but noticed a real issue with speed when using the effect in Firefox, so over the weekend, I set about addressing the problem. Here’s what I came up with:

What causes the issue?

The techniques used to achieve the parallax effect essentially boil down to hooking into the document’s scroll event and adjusting element properties based on the position of the vertical scroll bar. If you’re a Firefox user (especially if you’re on a PC) you’ve probably noticed that these sites appear to lag or jump around while you scroll. Generally speaking, these sites lack the slick, responsive experience you get from Safari, Chrome, Opera or IE.

The reason Firefox struggles to render parallax scrolling websites at a reasonable speed is because it doesn’t dispatch scroll events anywhere near as frequently as the other browsers. This means the script responsible for modifying element properties doesn’t execute as often and this ruins the parallax effect.

Increasing the scroll event frequency

The idea is to make the scrolling more responsive without changing the way events are added to the document and avoiding the need for helper functions with code paths for different browsers. All I want to do is increase the rate at which Firefox dispatches scroll events.

Increasing the scroll event frequency requires creating and dispatching a custom DOM scroll event. Creating the event is easy, the big problem is; how to dispatch the event while the user is scrolling the page?

The solution lies in a Firefox specific “feature”. Firefox triggers mouse events inside scroll bar tracks. Exploiting this feature allows us to detect mouse movement in the document’s vertical scroll bar using the mousemove event. Since mousemove fires every time the mouse moves, it can be used to dispatch a custom scroll event to the document giving us the increased frequency required to achieve a fluid parallax effect.

The second step is to sync the mouse-wheel scroll repaints. To do this I’ve added a handler to the Gecko specific DOMMouseScroll event. The handler disables the default scrolling action and dispatches the custom scroll event. Since the default action is disabled, I also have to recreate the scrolling effect by modifying the document.documentElement.scrollTop property based on the event data.

The script

Here it is:

/*
Firefox super responsive scroll (c) Keith Clark - MIT Licensed
*/
(function(doc) {

  var root = doc.documentElement,
      scrollbarWidth, scrollEvent;

  // Not ideal, but better than UA sniffing.
  if ("MozAppearance" in root.style) {

    // determine the vertical scrollbar width
    scrollbarWidth = root.clientWidth;
    root.style.overflow = "scroll";
    scrollbarWidth -= root.clientWidth;
    root.style.overflow = "";

    // create a synthetic scroll event
    scrollEvent = doc.createEvent("UIEvent")
    scrollEvent.initEvent("scroll", true, true);

    // event dispatcher
    function scrollHandler() {
      doc.dispatchEvent(scrollEvent)
    }

    // detect mouse events in the document scrollbar track
    doc.addEventListener("mousedown", function(e) {
      if (e.clientX > root.clientWidth - scrollbarWidth) {
        doc.addEventListener("mousemove", scrollHandler, false);
        doc.addEventListener("mouseup", function() {
          doc.removeEventListener("mouseup", arguments.callee, false);
          doc.removeEventListener("mousemove", scrollHandler, false);
        }, false)
      }
    }, false)

    // override mouse wheel behaviour.
    doc.addEventListener("DOMMouseScroll", function(e) {
      // Don't disable hot key behaviours
      if (!e.ctrlKey && !e.shiftKey) {
        root.scrollTop += e.detail * 16;
        scrollHandler.call(this, e);
        e.preventDefault()
      }
    }, false)

  }
})(document);

Try it out

Visit the Nike Better World and Iutopi websites (any parallax site will do) in Firefox and scroll through them (noting the lag), then paste the JavaScript above into the console (Firebug) and execute it. Scroll again, things should look much smoother — especially if you’re using a PC.

Personal Achievements

  • 2015 JS1k – winner
  • 2014 Net Awards: Demo of the year – winner
  • 2014 Net Awards: Developer of the year – longlist
  • 2013 Public speaking for the first time
  • 2011 .net Magazine innovation of the year – shortlist

Referenced in…

Smashing CSS, CSS3 for web designers, Programming 3D Applications with HTML5 and WebGL and more.

I am very proud to have had my work referenced in a number of publications, written by some very talented people.