Parallax Effect Using Only CSS With No JavaScript

Parallax is a visual effect, which can be created using layered graphics to imitate depth or distance.

In this article, I’ll introduce to you the CSS3 @keyframes rule, and demonstrate how to create a CSS3 animated background parallax effect without using any JavaScript.

Demo 1 | Demo 2 | Demo 3 | Demo 4 | Demo 5

The CSS3 @keyframes rule is a recent addition and requires the latest web browsers. If the clouds in the image above aren’t moving, you need to update your browser, or switch to another. Internet Explorer will not implement the @keyframes rule until version 10 and higher.

The parallax effect is somewhat easy to achieve using the @keyframes rule and a couple of transparent png images.

The @keyframes Rule

For example, imagine the @keyframes rule as an animated movie clip. In this animated clip, we have an object, named Bob, and we want Bob to move from one side of the screen to the other. Also, we want the duration of the animation to be 60 seconds. For the purpose of this example, I will name the clip, “Bobs-Animation”.

Bob

This is Bob.

The @keyframes rule contains all of the actions which will occur during Bob’s animation. The actions defined in the @keyframes rule will take place within a set amount of time.

The @keyframes rule is basically divided into segments, which define what occurs at certain points during the clip.

For example, at the beginning of the clip (0%), we want Bob’s left position to be 0 pixels, in the middle of the clip (50%), we want Bob’s position to be 325 pixels, and at the end of the clip (100%), we want Bob’s position to be 650 pixels. We also want Bob to maintain a position of 0 pixels from the top of the viewport.

Below is how the @keyframes rule would be setup for Bob’s animation.

@keyframes Bobs-Animation {
  0% {
    top: 0px;
    left: 0px;
  }
  50% {
    top: 0px;
    left: 325px;
  }
  100% {
    top: 0px;
    left: 650px;
  }
}

Looking at the @keyframes rule above, you will see I named the @keyframes rule, “Bobs-Animation”. Each @keyframes rule must be given a unique name so it can be identified. In the clip, Bobs-Animation, we defined Bob’s position at three different points during the clip, 0%, 50%, and 100%.

In addition to the @keyframes rule, we also need to define the settings for Bob, associate the “Bobs-Animation” rule with Bob, and the playback settings for the clip.

.Bob{
    animation: Bobs-Animation 60s linear;
    animation-iteration-count: infinite;
    height: 116px;
    width: 60px;
}

Above, we defined Bob’s settings, including those for Bobs-Animation.

animation: Bobs-Animation 60s linear; // Name, duration seconds, easing function
animation-iteration-count: infinite; // Number of times to run the animation

Now, when the clip loads, Bob will travel from left to right, completing the animation in 60 seconds. With animation-iteration-count set to infinite, the animation will automatically replay an infinite number of times.

That’s the @keyframes rule in a nutshell. Before attempting to use any of the above examples, be sure to read the rest of this article, especially the portion about the browser-specific proprietary prefix.

Reference: CSS animation Property The @keyframes rule

The Parallax Demo

For the parallax demo, I made two images of clouds. Each image is PNG32 format with a transparent background, as seen below.

Cloud 1

Cloud 1

Cloud 2

Cloud 2

Next, I set up my HTML document with four background layers, each having a unique class name. The unique class name allows other scripts to be able to identify the object.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
	<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
	<meta name="author" content="JasonLau.biz" />
	<title>CSS Parallax Effect - JasonLau.biz</title>
    <link href="style.css" rel="stylesheet" type="text/css" />
</head>

<body>
<div class="background1"></div>
<div class="background2"></div>
<div class="background3"></div>
<div class="background4"></div>
</body>
</html>

Next, I created the stylesheet file, style.css, where all of the real work for the parallax effect takes place.

Each of the HTML background layers I previously created will use basically the same CSS settings, with only slight variations in position and speed.

I’ll begin by defining the @keyframes rule for an individual layer.

@-webkit-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@-moz-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@-ms-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@-o-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}

As you can see there are actually five @keyframes rules in the above example. Each of these rules holds the same settings, but work for different web browsers. This is important for compatibility between web browsers. Until a standard is reached between all browsers, each browser requires a different proprietary prefix.

A separate rule must be created for each browser using the browser’s unique proprietary prefix, listed below, or the animation simply will not work in all possible browsers.

  • -webkit- Safari
  • -moz- Mozilla Firefox
  • -ms- Internet Explorer
  • -o- Opera

I’ll explain the @keyframes rule again.

Each layer is given it’s own unique @keyframes rule with a unique name. eg., animate_background1, animate_background2, animate_background3, etc.. In this instance, the @keyframes rule defines the settings for an animation named animate_background1.

The first portion of the rule defines an object’s CSS settings for the beginning of the animation, 0%.

0% {
    background-position: 0 -2px;
  }

The second portion defines an object’s CSS settings for the end of the animation, 100%.

100% {
    background-position: 257px -2px;
  }

These CSS settings could be for opacity, dimensions, color, position, or anything. In this article, I’m using the @keyframes rule to change background-position for each layer. 257px is the width of the image I am using. I use the image width for this setting so the animation does not jump or skip. -2px is the position of the image relative to the top of the container.

Now that the @keyframes rule has been defined, the settings for the layer itself must be defined using it’s unique class name, .background1, as the selector.

.background1 {
    position: fixed;
    z-index: 1;
    margin: 0px auto 0px auto !important;
    padding: 0px 0px 0px 0px;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    min-height: 100% !important;
    background: url("cloud-1.png");
    background-repeat: repeat-x;
    -webkit-animation: animate_background1 40s linear;
    -webkit-animation-iteration-count: infinite;
    -moz-animation: animate_background1 40s linear;
    -moz-animation-iteration-count: infinite;
    -ms-animation: animate_background1 40s linear;
    -ms-animation-iteration-count: infinite;
    -o-animation: animate_background1 40s linear;
    -o-animation-iteration-count: infinite;
    animation: animate_background1 40s linear;
    animation-iteration-count: infinite;
    opacity:0.80;
    filter:alpha(opacity=80);
}

I’ll explain these settings further.

  1. Position is fixed so the animation stays in place when the page is scrolled.
  2. z-index is the layer’s stacking order. 1 is bottom, and it goes up from there.
  3. Margin is zero so there are no gaps around the outer edge of the layer.
  4. Width and height are 100% to fill the entire page.
  5. Background-repeat, repeat-x, causes the image to tile horizontally only.
  6. Opacity and filter define the transparency for the layer.
  7. animation, and animation-iteration-count define the animation name and behavior. 50s is the number of seconds the animation will run, linear is the motion easing, and infinite is the number of iterations. These rules must be prefixed for browser compatibility

As I mentioned previously, each layer will have it’s own set of CSS rules and settings. To achieve the parallax effect, each layer should be positioned slightly different, and should be placed according to the speed which it will be travelling. The faster an object travels, the higher in the stacking-order it should be. So, the faster the object travels, the higher it’s z-index setting should be. Also, the faster objects should be incrementally positioned either lower or higher on the page. Placing the slower objects behind the faster objects will create the illusion of depth, and a visual effect, which appears much like looking through the window of a travelling automobile.

So, here is the CSS code for all four of the layers.

@-webkit-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@-moz-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@-ms-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@-o-keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}
@keyframes animate_background1 {
  0% {
    background-position: 0 -30px;
  }
  100% {
    background-position: 257px -30px;
  }
}

.background1 {
    position: fixed;
    z-index: 1;
    margin: 0px auto 0px auto !important;
    padding: 0px 0px 0px 0px;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    min-height: 100% !important;
    background: url("cloud-1.png");
    background-repeat: repeat-x;
    -webkit-animation: animate_background1 40s linear;
    -webkit-animation-iteration-count: infinite;
    -moz-animation: animate_background1 40s linear;
    -moz-animation-iteration-count: infinite;
    -ms-animation: animate_background1 40s linear;
    -ms-animation-iteration-count: infinite;
    -o-animation: animate_background1 40s linear;
    -o-animation-iteration-count: infinite;
    animation: animate_background1 40s linear;
    animation-iteration-count: infinite;
    opacity:0.80;
    filter:alpha(opacity=80);
}

@-webkit-keyframes animate_background2 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 259px -50px;
  }
}
@-moz-keyframes animate_background2 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 259px -50px;
  }
}
@-ms-keyframes animate_background2 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 259px -50px;
  }
}
@-o-keyframes animate_background2 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 259px -50px;
  }
}
@keyframes animate_background2 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 259px -50px;
  }
}

.background2 {
    position: fixed;
    z-index: 2;
    margin: 0px auto 0px auto !important;
    padding: 0px 0px 0px 0px;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    min-height: 100% !important;
    background: url("cloud-2.png");
    background-repeat: repeat-x;
    -webkit-animation: animate_background2 20s linear;
    -webkit-animation-iteration-count: infinite;
    -moz-animation: animate_background2 20s linear;
    -moz-animation-iteration-count: infinite;
    -ms-animation: animate_background2 20s linear;
    -ms-animation-iteration-count: infinite;
    -o-animation: animate_background2 20s linear;
    -o-animation-iteration-count: infinite;
    animation: animate_background2 20s linear;
    animation-iteration-count: infinite;
    opacity:0.6;
    filter:alpha(opacity=60);
}

@-webkit-keyframes animate_background3 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 257px -50px;
  }
}
@-moz-keyframes animate_background3 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 257px -50px;
  }
}
@-ms-keyframes animate_background3 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 257px -50px;
  }
}
@-o-keyframes animate_background3 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 257px -50px;
  }
}
@keyframes animate_background3 {
  0% {
    background-position: 0 -50px;
  }
  100% {
    background-position: 257px -50px;
  }
}

.background3 {
    position: fixed;
    z-index: 3;
    margin: 0px auto 0px auto !important;
    padding: 0px 0px 0px 0px;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    min-height: 100% !important;
    background: url("cloud-1.png");
    background-repeat: repeat-x;
    -webkit-animation: animate_background3 15s linear;
    -webkit-animation-iteration-count: infinite;
    -moz-animation: animate_background3 15s linear;
    -moz-animation-iteration-count: infinite;
    -ms-animation: animate_background3 15s linear;
    -ms-animation-iteration-count: infinite;
    -o-animation: animate_background3 15s linear;
    -o-animation-iteration-count: infinite;
    animation: animate_background3 15s linear;
    animation-iteration-count: infinite;
    opacity:0.8;
    filter:alpha(opacity=80);
}

@-webkit-keyframes animate_background4 {
  0% {
    background-position: 0 -75px;
  }
  100% {
    background-position: 259px -75px;
  }
}
@-moz-keyframes animate_background4 {
  0% {
    background-position: 0 -75px;
  }
  100% {
    background-position: 259px -75px;
  }
}
@-ms-keyframes animate_background4 {
  0% {
    background-position: 0 -75px;
  }
  100% {
    background-position: 259px -75px;
  }
}
@-o-keyframes animate_background4 {
  0% {
    background-position: 0 -75px;
  }
  100% {
    background-position: 259px -75px;
  }
}
@keyframes animate_background4 {
  0% {
    background-position: 0 -75px;
  }
  100% {
    background-position: 259px -75px;
  }
}

.background4 {
    position: fixed;
    z-index: 4;
    margin: 0px auto 0px auto !important;
    padding: 0px 0px 0px 0px;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    min-height: 100% !important;
    background: url("cloud-2.png");
    background-repeat: repeat-x;
    -webkit-animation: animate_background4 10s linear;
    -webkit-animation-iteration-count: infinite;
    -moz-animation: animate_background4 10s linear;
    -moz-animation-iteration-count: infinite;
    -ms-animation: animate_background4 10s linear;
    -ms-animation-iteration-count: infinite;
    -o-animation: animate_background4 10s linear;
    -o-animation-iteration-count: infinite;
    animation: animate_background4 10s linear;
    animation-iteration-count: infinite;
    opacity:0.6;
    filter:alpha(opacity=60);
}

Looking at the above code, you will see there are slight differences in the settings for each layer, which creates the parallax effect.

That is all the coding for the parallax; four files and a little bit of CSS. No JavaScript. Enjoy.

  • http://www.facebook.com/alan.mccarthy Alan McCarthy

    Hi Jason, love this effect. How would you get one of the layers to scroll from right to left though?

    Many thanks,
    Al

  • http://BullyStud.com/ American Bully

    Hi Al,

    In the example, the starting position is -
    0% {
    background-position: 0 -30px;
    }

    … and the end position is -
    100% {
    background-position: 257px -30px;
    }

    To reverse a layer, reverse the start and end numbers for the background-position -

    0% {
    background-position: 0 -30px;
    }
    … becomes -
    0% {
    background-position: 257px -30px;
    }

    … and -
    100% {
    background-position: 257px -30px;
    }
    … becomes -
    100% {
    background-position: 0 -30px;
    }

    The first number is the left position relative to the left-edge of the parent container and the second number is the position relative to the top-edge of the parent container.

    To get an object to travel from left to right, the starting position must be beyond the right-edge of the parent container, and the end position must be zero minus the width of the object. For example, if the parent container is 500 pixels wide, and the animated object is 100 pixels wide, the start position would be 500px, and the end position would be -100px.

    Hope that helps!

  • http://www.facebook.com/alan.mccarthy Alan McCarthy

    great thanks!

  • Andy Taylor

    Just came across this – I know its relatively old, but I only started looking into parallax effects today ;).

    Question…
    Can you use this effect to make a horizontal menu do what yours does on this site?

    What I mean is that the menu initially sits at (e.g) 100 pixels below the top of the screen, but when the user scrolls below that, the menu fixes itself into position at the top whilst content scrolls behind it

  • http://BullyStud.com/ American Bully

    Hi, Have a look at this jquery plugin I made -
    http://jasonlau.biz/home/jquery/detach-it

    Hopefully, it will help you accomplish what you are wanting to achieve.