Dwarves
Memo
Type ESC to close search bar

Pure CSS Parallax

This article demonstrates how to use CSS transforms, perspective and some scaling trickery to create a pure CSS parallax scrolling website.

Advantages of using pure CSS over JS

Although using Javascript will give us more flexibility on how we want to construct our parallax effect, it also comes with the cost of performance & implementation complexity. We listen to the scroll event & modify the DOM with the handler, triggering needless reflows and paints.

For more simple use cases, with pure CSS, we can:

How it works

First, let’s establish some barebones markup:

<div class="parallax">
  <div class="layer layer-1">...</div>
  <div class="layer layer-2">...</div>
  ...
</div>

And the basic styles:

.parallax {
  perspective: 1px;
  height: 100vh;
  overflow-x: hidden;
  overflow-y: auto;
}

.layer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.layer-1 {
  transform: translateZ(0);
}

.layer-2 {
  transform: translateZ(-1px);
}

The parallax class is where the parallax magic happens:

The layer class defines a layer of content to which the parallax effect will be applied. The absolute position is optional, for the sake of display. You’ll see what I meant in a moment.

Finally, the layer-{{n}} class is used to set Z offset of the layers. If we consider the parallax container is a camera viewport, the Z offset will determine whether a layer is farther away, or closer to the viewport. The farther away a layer is, the slower it’ll appear to be scrolling.

Check it out in the CodePen below:

Common practices

Parallax with multiple sections

Most parallax sites break the page into distinct sections where different effects can be applied. Here’s how to do that.

First, we need a group element to group our layers together:

<div class="parallax">
  <div class="group">
    <div class="layer layer-1">Layer 1.1</div>
    <div class="layer layer-2">Layer 1.2</div>
    <div class="layer layer-3">Layer 1.3</div>
  </div>
  ...
</div>

And now the styles:

.group {
  ...
  transform-style: preserve-3d;
}

The property transform-style: preserve-3d prevents the browser from flattening the layer elements, indicating that children of the element should be positioned in the 3D-space. More on the property here.

One important rule to keep in mind when grouping elements is, we cannot clip the content of a group. Setting overflow: hidden on a group will break the parallax effect. Unclipped content will result in descendant elements overflowing, so we need to be creative with the z-index values of the groups to ensure content is correctly revealed/hidden as the visitor scrolls through the document.

Depth correction

True to 3D transforms, elements that are farther away from the viewport will appear smaller than those that are closer. If we want them to appear to be rendered as their original size (e.g. same font-size and all), we can use the scale transform to do that:

.layer-2 {
  transform: translateZ(-1px) scale(2);
}

The scale factor can be calculated with the following formula:

scale = 1 + (translateZ * -1) / perspective

Debugging

When you are working with parallax, it can be easier to get lost among the different layers. Taking a different perspective will allow you to know where everything is in the 3D space - which you can do by applying simple transform to the group elements:

.group {
  transform: translate3d(700px, 0, -800px) rotateY(30deg);
}

You can try out all the common practices I have mentioned in the CodePen below:

References