Categories:

Creating a basic parallax scrolling effect using CSS and JavaScript

Updated: July 24th, 16

The most dominate trend of 2015 in web design undoubtedly is the parallax scrolling effect, with no signs that's waning. Parallax scrolling transforms the page into a fun slideshow where different elements on the page move at different speeds relative to the scrolling of the page. In this tutorial we'll familiarize ourselves with how a basic parallax scrolling page is created in CSS and JavaScript, and gain insight into the whole shebang in the process.

The anatomy of a parallax scrolling effect

Wikipedia succinctly defines a parallax scrolling effect as:

"a special scrolling technique in computer graphics, wherein background images move by the camera slower than foreground images, creating an illusion of depth in a 2D video game and adding to the immersion."

As it pertains to a webpage, a parallax effect is tied to the scrolling of the page; as this action is performed, different elements on the page such as the background image and foreground elements move/ animate at a different pace relative to the scrollbar's, all orchestrated using JavaScript. Take a look at the following simple parallax scrolling example, which consists of a large background image plus 3 layers moving at different speeds relative to the scrollbar:

Demo: Simple parallax scrolling effect

The JavaScript used in any parallax effect effectively all do the following two things at a minimum:

  • Monitors precisely how much the document has been scrolled and the rate of change, by examining key properties such as window.pageYOffset.
  • Animate various elements on the page as the document is scrolled and relative to the scrollbar by calling code inside window's onscroll event.

Lets explain step by step now how the above parallax effect is put together, and through that, take the mystic out of it!

Starting page with background and bubbles

To start, we'll construct the basic page with just the deep sea background and the two large bubbles, without the fish or JavaScript getting in our way:

Demo: Initial page with two bubbles

The HTML markup is barebones:

<body>
<div id="bubbles1"></div>
<div id="bubbles2"></div>
</body>

Observe the different layers on the page and how they are positioned. The BODY element is simply used to display the large deep sea background image:

body{
 height: 2000px;
 background: navy url(deepsea.jpg) top center no-repeat fixed;
 background-size: cover;
}

The background-size: cover CSS3 property ensures that the image covers the entire area of the element; it makes for light work of plastering every inch of our BODY with the background image, though this property is resource intensive, and should be used with restraint in parallax scrolling applications.

Then comes our two bubbles. Each one is rendered as a background image of a DIV that's fixed on the page and positioned at the top left corner of the page:

#bubbles1, #bubbles2{
 width: 100%;
 height: 100%;
 top: 0;
 left: 0;
 position: fixed;
 z-index: -1;
 background: url(bubbles1.png) 5% 50% no-repeat;
}

#bubbles2{
 background: url(bubbles3.png) 95% 90% no-repeat;
}

This anchors the two bubbles in view and at the precise coordinates set inside the background property regardless of whether the page is scrolled.

Parallaxing the bubbles

The stage is set to parallax the two bubble layers. When we scroll the page, we'll move each layer in the opposite direction of the scrolling, and at a different speeds:

Demo: Page with parallaxing bubbles

JavaScript:

<script>

// Create cross browser requestAnimationFrame method:
window.requestAnimationFrame = window.requestAnimationFrame
 || window.mozRequestAnimationFrame
 || window.webkitRequestAnimationFrame
 || window.msRequestAnimationFrame
 || function(f){setTimeout(f, 1000/60)}

var bubble1 = document.getElementById('bubbles1')
var bubble2 = document.getElementById('bubbles2')

function parallaxbubbles(){
 var scrolltop = window.pageYOffset // get number of pixels document has scrolled vertically 
 bubble1.style.top = -scrolltop * .2 + 'px' // move bubble1 at 20% of scroll rate
 bubble2.style.top = -scrolltop * .5 + 'px' // move bubble2 at 50% of scroll rate
}

window.addEventListener('scroll', function(){ // on page scroll
 requestAnimationFrame(parallaxbubbles) // call parallaxbubbles() on next available screen paint
}, false)

</script>

Lets break down what's going on here. Ignoring the requestAnimationFrame() method for now, first, we reference the two bubble layers by their IDs. Inside the parallaxbubbles() function, we move each bubble by a fraction of the current vertical scroll amount, thus causing the bubbles to move at a different speed relative to the scrolling. The negative operator added in front of the scrolltop variable causes each bubble to move in the opposite direction of the scroll.

Continuing on, we tap into the "scroll" event of the window object to execute code whenever the window gets scrolled. But instead of just calling parallaxbubbles() directly inside this event, we'll take a more roundabout route that favours performance over succinctness. And that route involves calling parallaxbubbles() indirectly, inside JavaScript's requestAnimationFrame() method. The later is a JavaScript method (with various prefixed versions depending on the browser) that accepts a function reference and executes that function on the next available screen repaint. In other words, it stutters the execution of the function if necessary until the browser is able to render a new frame on the screen, preventing needless calling of the target function that only degrades performance. Whenever we're associating code to window's scroll event, we can expect that code to be invoked in rapid succession- optimizing performance then is key, and wrapping any animation code inside requestAnimationFrame() is an important step to doing that.

A parallaxing fish that moves horizontally across the screen

So we now have a page with two parallaxing bubbles, each moving at reduced rates compared to the scrolling. There's no logic that dictates where the bubbles should be precisely on the page relative to how much the document has been scrolled.

For the next object we'll be parallaxing, lets arrange it so that the object glides from the left edge of the window to the right in sync with the scrollbar. When the scrollbar is at the very top, the object is at the left edge, gradually moving until the scrollbar is at the very end, when the object will be at the right edge. The fish object as it is will be positioned similarity to the other layers, but near the left and bottom of the window.

Demo: Page with parallaxing bubbles and fish

CSS:

#bubbles1, #bubbles2, #fish{
 width: 100%;
 height: 100%;
 top: 0;
 left: 0;
 position: fixed;
 z-index: -1;
 background: url(bubbles1.png) 5% 50% no-repeat;
}

#fish{
 left: -100%;
 background: url(fish.png) right 90% no-repeat;
}

JavaScript:

<script>

// Create cross browser requestAnimationFrame method:
window.requestAnimationFrame = window.requestAnimationFrame
 || window.mozRequestAnimationFrame
 || window.webkitRequestAnimationFrame
 || window.msRequestAnimationFrame
 || function(f){setTimeout(f, 1000/60)}

var bubble1 = document.getElementById('bubbles1')
var bubble2 = document.getElementById('bubbles2')
var fish = document.getElementById('fish')

var scrollheight = document.body.scrollHeight // height of entire document
var windowheight = window.innerHeight // height of browser window

function parallaxbubbles(){
 var scrolltop = window.pageYOffset // get number of pixels document has scrolled vertically 
 var scrollamount = (scrolltop / (scrollheight-windowheight)) * 100 // get amount scrolled (in %)
 bubble1.style.top = -scrolltop * .2 + 'px' // move bubble1 at 20% of scroll rate
 bubble2.style.top = -scrolltop * .5 + 'px' // move bubble2 at 50% of scroll rate
 fish.style.left = -100 + scrollamount + '%' // set position of fish in percentage (starts at -100%)
}

window.addEventListener('scroll', function(){ // on page scroll
 requestAnimationFrame(parallaxbubbles) // call parallaxbubbles() on next available screen paint
}, false)

window.addEventListener('resize', function(){ // on window resize
 var scrolltop = window.pageYOffset // get number of pixels document has scrolled vertically
 var scrollamount = (scrolltop / (scrollheight-windowheight)) * 100 // get amount scrolled (in %)
 fish.style.left = -100 + scrollamount + '%' // set position of fish in percentage (starts at -100%)
}, false)

</script>

We add a DIV with the ID "fish" to the page first (view demo page source code), then reference it using the "fish" variable in our JavaScript. What follows are two variables that get the total height of the document and the height of the browser window at the moment, respectively:

var scrollheight = document.body.scrollHeight // height of entire document
var windowheight = window.innerHeight // height of browser window

Inside the parallaxbubbles() function, we can calculate exactly how much of the scrollbar has been scrolled as a percentage of the entire scrollable track (where 0 means scrollbar is at the very top, 100% means at the very bottom) with this magic line:

var scrollamount = (scrolltop / (scrollheight-windowheight)) * 100 // get amount scrolled (in %)

The sub operation (scrollheight-windowheight), or subtracting the height of the window from the total height of the document, nets us the total distance the scrollbar is able to travel before it reaches the bottom of the document. It is as this point that we want our fish object to be at the right edge of the window.

When we divide scrolltop (how much the scrollbar has currently traveled) with (scrollheight-windowheight), we get, as a percentage of the total distance, how much the scrollbar has traveled. Multiplying that by 100 converts that information into a percentage value, where 0 means the scrollbar is at the very top, and 100 at the very end of the scrolltrack:

Now that we know how much the scrollbar has scrolled in percentage, we can directly feed that value as part of the fish layer's left property, moving it proportionally to how much the scrollbar has scrolled:

fish.style.left = -100 + scrollamount + '%' // set position of fish in percentage (starts at -100%)

The -100 value is added so the initial left position of the fish is -100%, hiding it from view. As the user scrolls the page, that value gradually increases until it reaches 0%. That's when the fish appears at the right edge of the window (the actual fish image appears as a background positioned to the very right inside the fish layer).

Next we'll address the big elephant in the room- parallax scrolling and mobile devices.

A word on mobile devices and parallax scrolling