Skip to Main Content

Parallax Column in Pure CSS

Earlier this year I came across this incredible technique for creating the parallax effect (where objects closer to the viewer appear to move faster than objects in the distance) using only CSS. I’m sure you’ve seen parallax on the Internets before, you know, that site where an image moves off screen more slowly than the text as you scroll down.

Parallax has become a popular technique to create depth and visual interest on websites these days, and traditionally it has been done using Javascript. Depending on the implementation, it has worked fairly well or has made scrolling down the page a painful, jerky experience. On mobile devices, the parallax experience is often less-than-stellar. Most parallax implementations listen for the browser’s onscroll event, and versions of iOS prior to 8, for example, don’t fire the onscroll event until the user has finished scrolling (maybe, technically, it doesn’t repaint the screen until after onscroll has finished, but visually it’s the same difference). This leads to the parallax elements jumping to position once the user pauses scrolling down the page which makes the parallax effect detracting and the site seem poorly optimized. In order to get parallax to work (for the most part) on mobile devices, one often has to implement aggravatingly complex solutions specific to mobile devices that use their special touch-based events.

Implementing a CSS solution largely addresses both performance concerns and separate implementations for desktop and mobile devices as it’s all handled by the browser’s rendering engine. It does, however, have one major drawback: browser support. Fortunately, this won’t always be the case and so becoming familiar with the technique now will help you implement smooth as monkey grease parallax effects for your hipster sites of the future. A pure CSS parallax implementation means way less code to achieve the effect, it’s easily adaptable to various screen sizes via CSS media queries, and it works well across the device spectrum.

For one of CodeGeek’s projects, it was going to be icing on the cake to make one of the columns in a three-column layout parallax, and so I decided that it could be a progressive enhancement that uses Modernizr to add the eye-candy for capable browsers. Most of the handful of CSS parallax examples that I could find out there were full-page examples which saw the entire world (page) through parallax-colored lenses, with no regard for a section of the page that wanted to behave normally. But what does it take to make just part of the page parallax? Here’s what I learned:

Parallax Container

First we need a containing element—likely one of the top-level containers for the page’s content as this will create a new scrolling context where the parallax effect takes place.

    #parallax-container {
      -webkit-perspective: 1px;
      perspective: 1px;
      -webkit-perspective-origin: 100vw 50%;
      -moz-perspective-origin: 100vw 50%;
      -ms-perspective-origin: 100vw 50%;
      perspective-origin: 100vw 50%;
      height: 100vh;
      overflow-x: hidden;
      overflow-y: auto;
    }
Scrollbar Perspective Issue
An important note about 100vw in the perspective-origin property. Why aren’t we just setting it to 100%? What’s the difference? Typically, there is no difference. But, once we set the perspective property on the parent element, it’s transformed into 3d space and the browser doesn’t quite know what to do with the scrollbar’s width as you can see by the gap that appears once the scrollbar is present in the above-image.This isn’t something I’d noticed initially as OSX allows scrollbars to be shown “automatically based on mouse or trackpad” which mimics the scrollbar appearance/behavior of iOS devices which hide them or float them on top of the page as you scroll. I tend to think this is a browser bug as positioning seems to work like you’d think it would when perspective isn’t used, regardless of the presence of the scrollbar.

It seems to work more reliably when we use a div (or other element) inside of the body tag instead of the body tag itself. Since my navigation bar in the left column is position: fixed, I couldn’t use the body tag, as applying a transform to a parent element causes child elements with position: fixed to behave like position: absolute which isn’t so cool when you really want that position: fixed behavior.

The #parallax-container element acts like the body element as it creates the primary scrolling context for the page (height: 100vh + overflow-y: auto). For the CodeGeek project, only the header (containing the navigation with a fixed position) existed outside of #parallax-container. To ensure that our #parallax-container acts as the main scrolling area for the window, we disable scrolling for thetag by adding the CSS:

    html,
    body {
      width: 100vw;
      height: 100vh;
      overflow: hidden;
    }

Parallax Column

The element that we’d like to apply the parallax effect to would use the .parallax class (which in our case would be the third column with the image and quote) has the following CSS:

    .parallax {
      position: absolute;
      width: 30%;
      height: 50vh;
      top: 0;
      right: 0;
      bottom: 0;
      left: auto;
      margin: 0;
      transform: translateZ(-2px) scale(3);
      -webkit-transform-origin-y: 100% 100% 0px;
      -moz-transform-origin: 100% 100% 0px;
      -ms-transform-origin: 100% 100% 0px;
      transform-origin: 100% 100% 0px;
    }

For the .parallax element, the translateZ transform is where the magic happens. It transforms our element into 3d space and, in this case, we’re setting it to be further away from the viewer. As in real life, an object further away from us appears to move slower than things nearer us. The further away the object is, the slower it appears to move. Hence, the further away we set our element in 3d space (the more negative the translateZ value), the slower it will scroll on the page. Conversely, the closer the element is (the more positive the translateZ value), the faster it will move.

However, objects further away appear smaller, and so is the case here. To make the element appear the same size as it was before moving it away from us in 3d space, we make it bigger! That’s where scale comes in. Keith Clark provides the formula 1 + (translateZ * -1) / perspective to calculate the scale factor.

In looking at the .parallax CSS, some of you may be thinking “Shouldn’t height be 100vh?” Well, I’m not 100% positive I know what was causing it, but for pages that didn’t have a tall content area, I could scroll well below the end of the content area. I think this was happening because when we’re scaling the element, it is also scaling the element’s height (100vh). However, you’d think that the parent’s overflow-x would cut off the excess height for us. It didn’t. Setting it to half of the height seemed to making things work like I’d expect them to.

This CSS Parallax technique requires both the perspective transform and preserve-3d CSS properties which aren’t supported up through IE 11 (but are supported in IE Edge 12 & 13).

For all of these styles I prefixed them with the class csstransformspreserve3d using a Modernizr test as I only wanted to apply the styles if the browser supported the preserve-3d CSS transform (remember, the effect was just icing on the cake). For those interested, here’s the Modernizr test I used:

Modernizr.addTest('csstransformspreserve3d', function () {
  var prop, val, cssText, ret;

  prop = 'transform-style';
  if ('webkitTransformStyle' in document.documentElement.style) {
    prop = '-webkit-' + prop;
  }
  val = 'preserve-3d';
  cssText = '#modernizr { ' + prop + ': ' + val + '; }';

  Modernizr.testStyles(cssText, function (el, rule) {
    ret = window.getComputedStyle ? getComputedStyle(el, null).getPropertyValue(prop) : '';
  });

  return (ret === val);
});
Modernizr.csstransformspreserve3d;

Please check out Keith Clark’s Pure CSS Parallax Websites article for a more detailed explanation of the effect.