Parallax scrolling with JS controlled CSS variables
CSS custom properties (often referred to as CSS variables) are quite new, but powerful. They — as the name suggests — behave like CSS properties and aren’t just variables like the ones you know from SASS:
- No preprocessor needed.
- They inherit and behave like CSS properties.
- You can access and manipulate them in JavaScript.
The last point is where it gets interesting. A combination of CSS and JS allows us to do the following:
header {
background: var(--color);
}footer {
background: var(--color);
}
In JS we change --color
to a random value. As we apply the property to the body, every property that uses --color
changes. In real time!
document.documentElement.style.setProperty('--color', randomColor())
An exciting behavior, which was enough reason for me to build a parallax scrolling library powered by CSS variables. The next chapters will explain how you can take advantage of it.
Browser support
Three major browsers already support CSS variables: Chrome, Safari and Firefox. Edge and older browsers are out of luck. Is this a problem? No! Parallax animations are a perfect example for progressive enhancement. Your site keeps working everywhere, but users with the latest browsers get some special effects 😎.
Getting started
basicScroll has zero dependencies, CommonJS and AMD support and support for mobile and desktop. It basically includes everything you need to create stunning parallax animations. Download basicScroll from GitHub or install the library with Bower or npm to get started:
npm install basicscroll
HTML & CSS
In order to use the library we first need HTML and CSS. Lets create something similar to the Firewatch website. A hero with multiple layers. A composition of illustrations that creates a parallax scene.
Here’s the HTML …
<img class="scene" data-modifier="30" src="p0.png">
<img class="scene" data-modifier="18" src="p1.png">
<img class="scene" data-modifier="12" src="p2.png">
<img class="scene" data-modifier="8" src="p3.png">
<img class="scene" data-modifier="6" src="p4.png">
<img class="scene" data-modifier="0" src="p6.png">
… and the CSS …
body {
/* Let the user scroll */
height: 2000px;
/* Frontmost scene and background should have the same color */
background: black;
}.scene {
position: absolute;
width: 100%;
transform: translateY(var(--translateY));
will-change: transform;
}
The .scene
is where it gets interesting: We are going to use --translateY
to change the position of each layer. The will-change
CSS property provides a way to hint browsers about the kind of changes to be expected on an element. This way browser can setup appropriate optimizations ahead of time. A big performance win in our example, as we already know that transform
will change when the users scrolls.
JavaScript
But without JS, nothing will happen 🤔 Our variable is currently empty and therefore won’t do anything. Browsers will ignore the property and use their default for transform
. Here’s where basicScroll comes in. What we want from basicScroll is that …
- … it starts to change the CSS custom property for each layer as soon as the user starts scrolling.
- … it stops to change the CSS custom property for each layer as soon as the bottom of the layers reaches the top of the viewport.
basicScroll works with instances. Each instance tracks one element or has a fixed start and end position. What we need is an instance for each layer:
document.querySelectorAll('.scene').forEach((elem) => {
const modifier = elem.getAttribute('data-modifier')
basicScroll.create({
elem: elem,
from: 0,
to: 519,
props: {
'--translateY': {
from: '0',
to: `${ 10 * modifier }px`,
direct: true
}
}
}).start()
})
0
tells the library to start changing the props
at 0px
. 519
on the other hand specifies that we don’t want any changes when the user scrolled more than 519px
. 519px
is the height of our scenes, but could be whatever you want.
The props
object contains the CSS properties that should change. In our case the --translateY
variable. The layers should move faster the more they are in the background of the illustration. The frontmost layer should have the slowest transformation, while the rearmost layer should have the fastest. That’s exactly what the modifier
is for.
And last but not least: The direct
property, which tells basicScroll to apply the variable directly on the element.
Everything we need to do now is to activate the instance by calling it’s start function. That’s it! 🎉 Enjoy the full demo on CodePen.
Artwork by my co-worker @setgraphic: Check out his work.