Lightweight Parallax Scrolling

Last year I was working on a clients site which needed very specific front-end effects on their homepage which included some parallax scrolling effects. I decided to write my own script for the parallax scrolling which I have made available on GitHub, I also have put up a demo of it here.

Here I’ll give a quick overview of how it works.

What is Parallax Scrolling

Parallax Scrolling is a technique used to give the perception of depth on a 2d screen. We create this effect by moving elements at different speeds and in different directions.

CSS Parallax Scrolling

You can easily add a parallax effect to an element by giving it a background image which does not scroll with the rest of the page.

section.background.top {
  background-image: url(images/sky-bg.jpg);
  background-attachment: fixed;
}

light-parallax-scroll.js

With the script I wrote you can easily add more parallax effects and animations to foreground elements rather than just on background elements with the css method above. I have only added four parallax scrolling effects to the script but more can easily be added. This script can be used as a boilerplate for creating your own parallax effects.

The theory behind it is simple, we just use javascript to move the foreground elements at different speeds and directions as the user scrolls the page. But this isn’t as straightforward as it seems and can be quite a heavy resource on the browser.

Parallax Animations

First thing is declare the variables which we will use.

// cache jQuery selectors to which the parallax animations will occur
var prlxDown = $('.prlx-down');
var prlxUp = $('.prlx-up');
var prlxDownRight = $('.prlx-down-right');
var prlxUpSpin = $('.prlx-up-spin');
// set the parallax value vars used for setting setting the speed and direction,
// it is calculated in the getYOffsetValue() function with the position of the Y scroll position.
// A positive value moves the element down the page or infront of the x axis,
// while a negative value moves it up the page or behind the x axis.
var prlxDownValue = .55;
var prlxUpValue = -.25;
var prlxUpSpinValue = .35;
var prlxDownRightValue = .45;
// declare the vars which will be used to hold the css transform property values in the parallax() function.
var prlxDownStr;
var prlxUpStr;
var prlxUpSpinStr;
var prlxDownRightStr;
// set a variable flag which will be used to check weather to run the animations or not.
var requesting = false;

With the scroll event listener we call the onScroll() function which in turn calls the parallax() function once it checks to see if the requesting flag is false, if it’s true it runs the debounce() function to temper the the amount of time the parallax animations run.

var killRequesting = debounce(function () {
    requesting = false;
}, 100);

// Start the parallax animations on the scroll event by calling the onScroll function.
window.addEventListener("scroll", onScroll, false);

// checks to see that the requesting flag is false before running the animations.
function onScroll() {
    if (!requesting) {
        requesting = true;
        // using requestAnimationFrame browser API to perfomr cheaper animations
        requestAnimationFrame(parallax);
    }
    killRequesting();
}

Then in the parallax function we run our animations by modifying the css transform properties

function parallax() {  // animations drawn on the page by changing the css transform properties
    // prlxDown
    prlxDownStr = "translate3d(0, " + getYOffsetValue(prlxDownValue) + "px, 0)";  // putting the transform property into a string
    $(prlxDown).css({  // applying parallax effect to element
        "transform":prlxDownStr,
        "-ms-transform":prlxDownStr,
        "-webkit-transform":prlxDownStr
    });

    // code omitted for brevity

    function getYOffsetValue(prlxValue) {
        // setting the speed/Value ation effect to be a multiple of the windows Y scroll position
        return ( window.pageYOffset * prlxValue ).toFixed(2)
    }

    if (requesting) {  // check the flag before calling itself again
        requestAnimationFrame(parallax);
    }
}

requestAnimationFrame

The script uses window.requestAnimationFrame which is a much cheaper method for creating animations on browsers than using a timer loop. To support older browsers which do not have native support for window.requestAnimationFrame I included a polyfill by opera engineer Erik Möller.

// A browser polyfill for requestAnimationFrame
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function () {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for ( var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
            || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
                timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

Scroll Event Listener

The scroll event listener can be quite taxing on browsers, especially with animations. So to overcome this I used the debounce function from Underscore to temper the the amount of time the parallax animations run.

// debounce is taken from _underscore.js
// http://underscorejs.org/#debounce
function debounce(func, wait, immediate) {
    var timeout, args, context, timestamp, result;
    return function() {
        context = this;
        args = arguments;
        timestamp = new Date();
        var later = function() {
            var last = (new Date()) - timestamp;
            if (last < wait) {
                timeout = setTimeout(later, wait - last);
            } else {
                timeout = null;
                if (!immediate) result = func.apply(context, args);
            }
        };
        var callNow = immediate && !timeout;
        if (!timeout) {
            timeout = setTimeout(later, wait);
        }
        if (callNow) result = func.apply(context, args);
        return result;
    };
}

Summary

The demo I have set up is pretty basic but you this script can easily be developed to include better parallax animations with a mixture of transform’s translate3d, scale, rotation and opacity animations.

If you wish you can check the demo to see how I applied css for this script.

You may also wish to add smooth scrolling to your site to give it a nicer effect, this method with TweenMax has worked well for me before.

Also if you have a lot of animations running on the page it is a good idea to stop them once the element is no longer in the browsers viewport, for this you could try the jquery-visible plugin or try this method.

It’s also important to consider how your parallax effexts will work on mobile or tablet devices, in some cases you may wish to disable the parallax effects on devices with smaller viewports or poorer performing browsers.