While doing some art direction on a recent post, I felt like the main header feature needed just a smidge of …*gulp*… parallax. Before you cast judgement, parallax is a nice effect sometimes… sometimes.. very sometimes.

I didn’t want to import some giant library, so after tinkering around in CodePen, here’s what I came up with:

See the Pen Forgive me, Lord. I made a Parallax thing. by Dave Rupert (@davatron5000) on CodePen.

With just a few lines of code, my heading “falls” slower than the rest of the page. It’s more impressive when it’s an animated vector illustration.

How it works

Inspired by some of the work of Dan Wilson, I’m creating the effect by manipulating CSS variables with JavaScript. Let’s dig into the code a little bit.

const title = document.querySelector('h1.title');
const speed = 0.2;

title.style.transform = 'translateY( calc( var(--scrollparallax) * 1px ) )';

window.addEventListener('scroll', function() {
  title.style.setProperty('--scrollparallax', (document.body.scrollTop || document.documentElement.scrollTop) * speed);
});

Add some .title { will-change: transform } to the CSS and we’re painting responsibly.

I’m pretty pleased that in ~6 lines of code I was able to achieve the parallax effect I wanted. I was able to quickly tinker with the prototype in order to get the speed and feel right.

Why use CSS Custom Properties?

Good question and I’m not sure I have great answer1. To me there’s something elegant about updating a single variable and handing the job over to CSS do the rest of the effect. CSS also has some more practical benefits…

  1. Disable parallax on mobile with @media queries.
  2. Disable parallax with @media (prefers-reduced-motion)
  3. Animate multiple items with one property.
  4. Use a single property for multiple effects.

Iterating off of the previous example, here’s another demo with all those things where we set a single --scroll-amount property on document.body.

window.addEventListener('scroll', function() {
  document.body.style.setProperty(
    '--scroll-amount',
    (document.body.scrollTop || document.documentElement.scrollTop)
  )
});

All the math rules are handed over to a calc() statement in CSS and we create all our magic off of the --scroll-amount property. Tossing in another --multiplier variable that changes on each element, we can make some pretty neat effects.

See the Pen Forgive me, Lord. I made a Parallax thing. Part 2 by Dave Rupert (@davatron5000) on CodePen.

Desktop-only mutli-effect parallax by setting 1 variable with JavaScript

Bingo bango! I like this solution because it’s simple enough that I understand what’s going on under the hood. “Simple” means I can modify, extend, and improve it as well2. I’m a big fan of finding the cheapest solution to create the minimum desired effect.

UPDATE: Tyson Matanich on Twitter suggested using requestAnimationFrame and it seems to squeeze out quite a bit more performance, holding 60FPS in Edge and the Chrome FPS meter holding at >30FPS.

  1. I had heard CSS Custom Properties were more performant than element.style but based on comparisons I’ve made, I’m not sure that’s true.

  2. Class-based triggers with Intersection Observer?