Pure-CSS Parallax Scrolling Effect

Joshua Bemenderfer

CSS is often more powerful than people give it credit for. A number of common tasks which generally rely on JavaScript libraries can often be implemented in CSS without much effort. A great example of this is the parallax effect which is currently in vogue. (Where background images scroll more slowly than the page content to give an illusion of distance.)

While there is no shortage of libraries available to produce this effect in JavaScript, this article will demonstrate a simpler CSS-only approach. (~35 lines)

We’ll be using images from placekitten as placeholder background images. If you didn’t already know about that site, just note that you now have to use kittens as placeholder images from now until the end of time.


There’s not much markup we’re going to need at all. Just this:

<main class="wrapper">
  <section class="section parallax bg1">
    <h1>Such Adorableness</h1>
  <section class="section static">
  <section class="section parallax bg2">
    <h1>SO FWUFFY AWWW</h1>

Here’s a breakdown of what each class is for:

  • .wrapper - Sets the perspective and scroll properties for the whole page.
  • .section - Size, display, and text properties. Mostly not relevant to the parallax effect.
  • .static - Adds a background to a section, just for demonstration.
  • .parallax - Adds an ::after pseudo-element with the background image and transforms needed for the parallax effect.
  • .bg1, .bg2 - Adds the respective background images for each section. (You could use an img tag instead.)


Now, on to the actual styling that makes the magic happen. Relevant lines are commented.

.wrapper {
  /* The height needs to be set to a fixed value for the effect to work.
   * 100vh is the full height of the viewport. */
  height: 100vh;
  /* The scaling of the images would add a horizontal scrollbar, so disable x overflow. */
  overflow-x: hidden;
  /* Enable scrolling on the page. */
  overflow-y: auto;
  /* Set the perspective to 2px. This is essentailly the simulated distance from the viewport to transformed objects.*/
  perspective: 2px;

.section {
  /* Needed for children to be absolutely positioned relative to the parent. */
  position: relative;
  /* The height of the container. Must be set, but it doesn't really matter what the value is. */
  height: 100vh;

  /* For text formatting. */
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  text-shadow: 0 0 5px #000;

.parallax::after {
  /* Display and position the pseudo-element */
  content: " ";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;

  /* Move the pseudo-element back away from the camera,
   * then scale it back up to fill the viewport.
   * Because the pseudo-element is further away, it appears to move more slowly, like in real life. */
  transform: translateZ(-1px) scale(1.5);
  /* Force the background image to fill the whole element. */
  background-size: 100%;
  /* Keep the image from overlapping sibling elements. */
  z-index: -1;

/* The styling for the static div. */
.static {
  background: red;

/* Sets the actual background images to adorable kitties. This part is crucial. */
.bg1::after {
  background-image: url('https://placekitten.com/g/900/700');

.bg2::after {
  background-image: url('https://placekitten.com/g/800/600');

That should be all you need! Here’s a working example.


  • It’s possible to put the images further away so that they move more slowly. You’ll have to fiddle with the perspective and the transform. There’s probably a more scientific way to do this too.
  • If you don’t want a background image to scroll at all, just use background-attachment: fixed; instead of perspective/translate/scale.
  • This technique doesn’t work well in iOS safari due to some of the optimizations of that browser.


  Tweet It

🕵 Search Results

🔎 Searching...