Skip to Content

A CSS Parallax Scroll Effect For All Modern Browsers

This article will teach you how to create a parallax scroll effect with pure CSS that works in all modern desktop and mobile browsers, including the latest versions of Chrome, Firefox, Edge, Opera, Safari, Chrome for Android, and Chrome and Safari for Mac.

Safari for Mac and Chrome and Safari for iOS have a minor caveat that you can simply workaround using a few CSS tricks. We'll go over that in a bit.

For now, let's get started with a quick explanation of what this is and how it can be used within a website.

What is a Parallax Scroll Effect?

Parallax scrolling is a technique used to make background images appear as if they're moving slower than their surrounding foreground elements when scrolling through a web page.

In this case, the illusion is accomplished by defining a set of 3D perspective and layer transformation CSS rules that will be output in a 2D format on the screen.

The result will be a smooth scroll with the background image scrolling at half the speed of its surrounding content:

CSS parallax scroll


Setting up the HTML Code

The below code example represents the markup needed for the background image, page title, and surrounding content:

<div class="parallax">
<h1>This is a Parallax Scroll Example</h1>

<div class="content-outer">
<div class="content-inner">
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
<p>Paragraph 4</p>
<p>Paragraph 5</p>

The .parallax container is the element where the background image will be stored using a pseudo-selector, and also where the page title will be displayed.

The .content-outer and .content-inner containers hold the page content that exists below the background image and page title.

The CSS Parallax Scroll Rules

Now, we'll step through each of the elements and their CSS rules and definitions.

Let's set up the html and body selectors.

First, we're removing all padding and margin spacing around the edge of our document. This is just to clean up the display:

html, body {
padding: 0px;
margin: 0px;

Second, the html selector removes all horizontal and vertical scrolling from the page and is required for the effect to work correctly:

html {
overflow: hidden;

And, third, we're setting the body width and height to the size of the viewport and enabling vertical scrolling if the page contents exceed the viewport height (which they will in this example) while keeping the horizontal scroll disabled.

The perspective rule is used to give the element a 3D-space by adjusting the distance between the user and the element on the Z-axis. This output does not affect the element itself, only its child elements and how they will be displayed.

And the transform-style rule with preserve-3d as its value confirms that the child elements of the body will maintain its 3D position:

body {
width: 100vw;
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
perspective: 1px;
-webkit-perspective: 1px;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;

Now, let's get down to the fun part!

The .parallax container is the element that will hold the background image displayed on the screen, as well as the page title.

Here, we're setting the element's size to 100% of the viewport width and height so that it covers the entire browser window.

Setting its position to relative ensures that the background image positioning will be retained to its parent's boundaries.

And, again, we're setting transform-style to preserve-3d to ensure that this container maintains its 3D positioning, as well:

div.parallax {
width: 100vw;
height: 100vh;
position: relative;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
Adding transform-style to the .parallax element was required for this effect to work correctly in Firefox only. This is because Firefox doesn't know to maintain 3D transformation rules within additional child elements and must be instructed to do so. You could also set this value to inherit for the same outcome.

And, finally, we have the .parallax:after pseudo-element definition that creates the rules for our background image and how it will be positioned in the .parallax container:

div.parallax:after {
background-image: url("/bg.jpg");
background-repeat: no-repeat;
background-position: center;
background-size: cover;
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: -1;
content: "";
transform: translateZ(-1px) scale(2);
-webkit-transform: translateZ(-1px) scale(2);

Here, we're setting the background image URL and its position within the .parallax element. The positioning must be absolute and centered.

Setting the top, left, right, and bottom rules to 0px sets the bounding box for the element and instructs the browser to fill all available space with the contents of that element. In this case, it will be the background image that fills the container.

Next, we're setting a negative z-index value for a few reasons: to ensure that there is no overflow of content from other elements within the page, and to slow down the scroll speed which we'll also accomplish with our next CSS rule.

Setting negative Z values with translateZ() ensures that the scroll speed will be slower than with a positive value by zooming out from its original position on the Z-axis. We then provide depth correction with the scale() rule, doubling the background image size so it appears rendered at its original size to the user.

The scale factor is calculated as ((translateZ * -1) + 1) / perspective. So, if our viewport perspective is set to 1px and we translate an element -1px along the Z-axis like we have in this example so far, the correction scale factor would be 2.

The Safari and iOS Caveat

Everything should now be working smoothly, at least in most browsers. The caveat is with Safari and iOS browsers where the content below the .parallax container overlaps the background image.

Generally, with the other browsers, you could set a CSS rule of overflow: hidden to the .parallax container to correct this issue. But, with Safari and iOS browsers, we need to take a different approach.

We can correct this by simply increasing the perspective value of the content below the .parallax element to 2px, 1 pixel larger than the .parallax element itself:

div.content-outer {
width: 100%;
background-color: white;
perspective: 2px;
-webkit-perspective: 2px;

The content will appear scaled as normal to the user but tricks the browser into thinking the content below the .parallax element is actually higher in the Z-axis, and therefore covers up the background image as needed.


The article explained how to create a simple parallax scroll effect with CSS in all modern browsers with a single set of rules.

You can download the complete source code from my GitHub repository. This codeset includes the assets from the examples above and will allow you to play around with different values as needed to further understand how each of the CSS rules works and interact with each other.

Last Updated: January 01, 2021
Created: December 06, 2020



Hi, thank you for this helpful tutorial. Do you have any idea how the on scroll event is triggered again in JS?


Josh Rowe

You can trigger a scroll event with JavaScript like this:
document.addEventListener("scroll", function(event) { ... });


Add A Comment

Comment Etiquette: Wrap code in a <code> and </code>. Please keep comments on-topic, do not post spam, keep the conversation constructive, and be nice to each other.