Pure CSS3 Parallax Header

Just to explain this tutorial a bit more in detail, I'm writing it up. An interesting fact, the collection of this knowledge is actually quite simplistic; the math behind it is quite complicated without proper representation though.

There are dozens of articles that explain the math, and all of them make valid points. I would highly recommend checking some out! Just search it up on Google, and you'll find a remarkable amount of research in this single area! Considering this is untamed territory of browsers, there are quite a number of bugs; I mention the ones relevant to this particular project, and the proper fixes. It may seem a bit confusing at first, until you realize that not even the browser is properly calculating the values. In the future, I am hoping there will be a solid fix to each one of these bugs, but until then we shall manage. I bet you now feel a little bit more comfortable with this just by me saying that, so let's move on. Here are the pages I referenced while making this tutorial:

Time to move into the actual tutorial!


For those of you that had not watched the video, or cannot, I shall state that the finished product of this tutorial is ready for production-level projects. It has an incredibly smooth animation that does not hinder performance in the slightest, and it will not induce any migraines!

Dependency:
My HTML5 Setup:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta http-equiv="x-ua-compatible" content="ie=edge" />

        <title></title>

        <link rel="icon" href="data:;base64,iVBORw0KGgo=" />
    </head>

    <body></body>
</html>
Basic CSS:
<style type="text/css">
    *,
    *::before,
    *::after {
        -webkit-box-sizing:border-box;
        -moz-box-sizing:border-box;
        box-sizing:border-box;
    }

    html,
    body,
    body > header,
    body > section {
        height:100vh;

        position:relative;
    }

    * {
        margin:0;
        padding:0;
    }

    html { overflow:hidden; }
    body {
        overflow-x:hidden;
        overflow-y:auto;
    }
</style>

First, throw that into the head. Second, I use this CSS setup in nearly every one of my projects. Without this setup, I have found that html and body don't play nicely together in several situations, considering html likes to do its own thing with overflow. This next helper class is one I use for easier manipulation of images:

.background-image {
    width:102%;
    height:100%;

    background-size:100%;
    background-repeat:no-repeat;
    background-position:center;

    top:0;
    left:0;
    right:0;
    bottom:0;
    z-index:-1;
    position:absolute;

    -webkit-pointer-events:none;
    -moz-pointer-events:none;
    pointer-events:none;
}
Testing Markup:
<header>
    <div class="background-image"></div>
    <div>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
        consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
        cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
        proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </div>
</header>
<section>
    <div>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
        consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
        cillum dolore eu fugiat nulla pariatur.</p>
    </div>
</section>

This is fairly straight forward. Throw this into the body and look over the remaining CSS.

Parallax CSS:

Now that all of the preliminary CSS is there, we can move onto fun stuff.

/**
 * Remember scale = 1 + (translateZ * -1) / perspective.
 * Set z-index:-1; to parallax background.
 * ----------------------------------------------------------
 * Set z-index:1; to all remaining groups that are not using parallax.
 */

@supports ((perspective: 1px) and (not (-webkit-overflow-scrolling: touch))) {
    body {
        -webkit-perspective:1px;
        -moz-perspective:1px;
        perspective:1px;

        perspective-origin:100%; // part 1 of a 2 part fix for Webkit browsers. Corrects bug in calculation of width.

        transform:translateZ(0); // fixes a bug in Edge browser, prevents error in rendering webpage.

        -webkit-transform-style:preserve-3d;
        -moz-transform-style:preserve-3d;
        transform-style:preserve-3d;
    }

    body > header { // this is a fix for Mozilla, as their definition of inherit is different.
        -moz-transform-style:inherit;
        transform-style:inherit;
    }
    body > header .background-image {
        transform:translateZ(-1px) scale(2); // scale = 1 + (translateZ * -1) / perspective

        transform-origin:100%; // part 2 of fix for Webkit browsers.

        background-image:url(./src/images/background.jpg);
    }

    body > section { z-index:1; }
}

Perspective is the distance, in pixels, a 3D element is placed from the view. A perspective of 1px is all that is required here, I never really used a higher one. Anything placed on the z-axis higher than its parent's perspective will not be rendered. Setting our transform-style to preserve-3d ensures the children of this element will be in a 3D space. The parallax element is placed further back on the z-axis, and is scaled to its original perceived size. For this, we need to use the calculation I have commented in the above code. Essentially with a 1px perspective, you only need to remember scale = 1 + -(translateZ). Finally, I have added the bug fixes in the comments of the code above. There are several, all of which have simple fixes.

Conclusion:

If it isn't already obvious, we've built a single moving element that has been placed in a 3D space, pushed it back and scaled it to its original perceived size. If it doesn't work, or you have some questions, just ask!