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.
- Creating a basic parallax scrolling effect using CSS and JavaScript
- A word on mobile devices and parallax scrolling