Last Updated:

How To Create a Parallax Scrolling Effect

If you want a site to cause a "wow effect" and share links to it, use parallax. With it, sites look voluminous, and elements can smoothly move when scrolling the page or moving the cursor.

In this article, we'll show you how to make a parallax in pure CSS and JavaScript, talk about some JS libraries, and see how to optimize animations.

Example of parallax on CSS
Example of parallax on CSS.

What to do the parallax effect on: CSS or JavaScript

Parallax when scrolling through pages is created using 3D transformations: transform-style: preserve-3d, perspective, transform: translateZ and others.

Making such effects in JavaScript is too resource-intensive, because the browser has to track the movement of the event, call the function of recalculating the position of the blocks and shift them. As a result, the pages slow down and the scroll becomes intermittent.

scroll

But parallax by mouse movement is created in JavaScript. CSS does not yet have suitable properties for this, but you can use it to optimize the parallax effect.

How to Do Parallax on Pure CSS

How to: Set an Element's Depth

To set the depth of an element, apply to it and specify the value of the .

transform: translateZ;perspective

Developers position the elements on the page along two axes: X and Y, vertically and horizontally. But there is also a third axis , Z, which reflects the depth of the element and its distance to the user. If we simply move the element along this axis, setting it the property , then we will not see the difference. The element is still in a two-dimensional plane, because the smartphone screen or laptop monitor do not have depth.

transform: translateZ

You can make the plane three-dimensional by using the CSS property . As a value, it takes the distance from the element to the user along the z axis - the greater this value, the farther away the element is from you, and vice versa. Often for specify a value of 1px - this is enough to establish the depth of perspective.

perspectiveperspective

Parallax example on pure CSS

Let's do parallax as you scroll through the page. First, we will prepare the marking of the block with parallax. Add — parent with class . Inside it, we create two layer elements with classes . The first element is with an image, the second is with text. Specify for the additional class , and for — .

<div>parallaxparallax-layer<div><span><div>parallax-image<span>parallax-text

<div class="wrapper parallax">
   <div class="parallax layer parallax image">
     <img src="<https://i.pinimg.com/originals/9e/20/fc/9e20fc9ba2e1456ff29caa6780521cb7.jpg>" alt="Garden of Fine Words">
   </div>
   <span class="parallax-layer parallax-text">Garden of Fine Words</span>
</div>

Set the -parent property . It creates a virtual 3D plane, indicating that the center of the block is the starting point of perspective construction. Add to scroll the child elements relative to a fixed point.

<div>perspective: 1pxparallaxoverflow-y: auto

.parallax {
  perspective: 1px;
  height: 100vh;
  overflow-y: auto;
}

Now remove the internal elements with the class from the general flow and stretch over the entire area of the parent.

parallax__layer

.parallax__layer {
  position: absolute;
  inset: 0; // вместо top, bottom, left, right: 0;
}

It remains to set the offset along the Z axis. The text will be further away from the user, and the background is closer - due to this we will create a parallax effect.

.parallax-image {
  transform: translateZ(0);
}

.parallax-text {
  transform: translateZ(-2px);
}

Add styles and get the result:

HTML

<!DOCTYPE html>
<html lang="en">
<head>
   <metacharset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="preconnect" href="https://fonts.googleapis.com">
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
   <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@500&display=swap" rel="stylesheet">
   <title>Css parallax</title>
</head>
<body>
   <div class="wrapper parallax">
     <div class="parallax layer parallax image">
       <img src="https://i.pinimg.com/originals/9e/20/fc/9e20fc9ba2e1456ff29caa6780521cb7.jpg" alt="Garden of Fine Words">
     </div>
     <span class="parallax-layer parallax-text">Garden of Fine Words</span>
   </div>
</body>
</html>

SCSS

body {
  margin: 0;
  font-family: 'Noto Sans', sans-serif;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

.wrapper {
  max-width: 1440px;
  margin: 0 auto;
  width: 100%;
}

.parallax {
  position: relative;
  perspective: 1px;
  height: 100vh;
  overflow: hidden;
  overflow-y: auto;
}

.parallax-layer {
  position: absolute;
  inset: 0;   
  height: 100%;
}

.parallax-image {
   transform: translateZ(0);
   object-fit: cover;
  
  img {
    @media(max-width: 768px) {
        height: calc(100% + 300px);
        object-fit: cover;
    }
  }
}

.parallax-text {
  font-size: 72px;
  color: #cde4f7;
  transform: translateZ(-2px) translateX(100px) translateY(300px) scale(3);
  
  @media (max-width: 768px) {
    font-size: 62px;
    transform: translateZ(-2px) translateX(20px) translateY(300px) scale(3);
    }
}

If you open a tab with CSS, you can see that the block is set — that is, the element is three times magnified. Why did we do that?parallax-textscale(3)

The fact is that the element, moving away from us in the 3D plane, visually decreases in size. Conversely, as it approaches, it increases. To compensate for these changes, we use . And its value is calculated by the formula:scale

1 + (translateZ * −1) / perspective

In our case, the calculation will be as follows:

1 + (-2 * −1) / −1

We achieved the parallax effect on pure CSS. All used properties are supported by modern browsers. If desired, you can add other elements to the block and play with their displacement along the Z axis. The main thing is not to forget that when you change the position on this axis, the dimensions of the element also change, so the value must be adjusted.parallaxscale

How to do parallax in JavaScript

Now let's create a parallax effect in JavaScript: make a card with several elements that will shift when the cursor moves - each at its own speed. Since the elements shift along the X and Y axes, this will be a 2D parallax.

First, let's write markup similar to the one we used in the previous example. We need a wrapper and internal animated elements:<div>

<div class="parallax">
  <div class="parallax__inner">
    <h1 class="parallax__layer title">
      Здорово быть енотом!
    </h1>
        
    <button class="parallax__layer button" type="button">
	 Стать енотом
    </button>
        
    <div class="parallax__layer circle"></div>
  </div>
</div>

Styles:

.parallax__inner {
  position: relative;
  overflow: hidden;
}

.parallax__layer {
  transition: transform 0.3s linear;
}

You can already specify the nature of the animation when offsetting elements by specifying a property for parallax elements. For more fine-tuning, you can use Bézier curves. But we do not recommend using the value: "jerks" may appear with a rapid movement of the cursor, because the function does not have sufficient linearity.transitionease-in-out

Let's move on to JavaScript. Find the parallax parent and all the parallax elements, and then add a handler to the parent. Let's listen to the movement of the mouse cursor — .mousemove

const wrapper = document.querySelector('.parallax__inner');
const layers = document.querySelectorAll('.parallax__layer');

wrapper.addEventListener('mousemove', initParallax);

Let's describe the function in which we will conduct calculations. Next, decompose the task: first, we will find the coordinates of the cursor relative to the card, and then calculate the new coordinates for the elements when the mouse event is triggered.initParallax

Always calculate the coordinates relative to the block on which the mouse event is listened to - then the calculations will be accurate. If you calculate the coordinates of the cursor relative to the viewport, the offset of the parallax elements will be calculated incorrectly, since the proportions of the viewport and the card will most likely not coincide.

First, let's calculate the coordinates. In JS, there is no method that returns the coordinates of the cursor relative to the desired block. The X-axis property returns the position relative to the beginning of the viewport. To make the beginning of the block coincide with 0 on the X axis, subtract the left indent of the block from the position relative to the screen. In this we will help the method .clientXparallaxparallaxgetBoundingClientRect()

Let's move on to calculations. For convenience, write the current coordinate to the variable and then add a variable with the outer indentation of the block from the borders of the screen. Let's subtract the indent from the current cursor position and write it to a variable:parallaxLeftOffsetwrappercoordX

 

const initParallax = (evt) => {
  const clientX = evt.clientX;
  const clientY = evt.clientY;

  const parallaxLeftOffset = wrapper.getBoundingClientRect().left;
  const coordX = clientX - parallaxLeftOffset;
  const coordY = clientY - parallaxTopOffset;
}

Now the left border of the parallax block coincides with the coordinate 0. This is not entirely correct, because we can only track cursor changes to the right. It is necessary to make the center of the block coincide with the coordinate 0. To do this, additionally subtract half the width of the block.

const coordX = clientX - parallaxLeftOffset - (0.5 * wrapper.offsetWidth);
const coordX = clientY - parallaxTopOffset - (0.5 * wrapper.offsetHeight);

To get the width, use the . Now the center of the wrapper is a point with coordinates 0.0. You can move on to the most interesting.offsetWidth

We calculated the position of the cursor relative to the parallax block. Now we can calculate the displacement of its elements and set it to them:

layers.forEach((layer)=>{
  const x = (coordX).toFixed(2);
  const y = (coordY).toFixed(2);
  layer.setAttribute('style', `transform: translate(${x}px, ${y}px);`)
});

Round the coordinate using the method and give each element a transformation along both axes. Here's what happened: toFixed

The elements now follow the cursor. Add a velocity factor that will slow down the transformation of the elements. Let it be equal to 0.5 - it is better not to set too large a value, since the transformation should be smooth.

Let's multiply the calculated coordinate by this coefficient.

 

The code works, but we've forgotten the essence of parallax. Parallax is the displacement of elements at different speeds, and now all elements move at the same speed.

Let's change that. The speed factor will be stored directly in the markup in the data-attribute, as it is convenient.

<div class="parallax">
  <div class="parallax__inner">
    <h1 class="parallax__layer title" data-speed="0.03">
      Здорово быть енотом!
    </h1>
        
    <button class="parallax__layer button" type="button" data-speed="0.05">
      Стать енотом
    </button>
        
    <div class="parallax__layer circle" data-speed="0.18"></div>
  </div>
</div>

The value should not be too large for the elements to move smoothly. Let's add the script with a speed adjustment:

layers.forEach((layer)=>{
    const layerSpeed = layer.dataset.speed;
    const x = (coordX * layerSpeed).toFixed(2);
    const y = (coordY * layerSpeed).toFixed(2);
    layer.setAttribute('style', `transform: translate(${x}px, ${y}px);`)
});

Now the elements move at different speeds. Final touch: Invert the value of the offset coordinates so that when you move the mouse to the left, the elements shift to the right, and vice versa. Ready!

 

How to Optimize Parallax

At the beginning of the article, we mentioned that parallax is resource-intensive for the browser - for each mouse movement, several commands are called to recalculate the coordinates. What can be done about it?

Do not offset elements by using properties. Instead, it is better to use them - they reduce the load on the graphics processor.top, left, right, bottomtransform: translateX, translateY

CSS has a will-change property. If you set it to , the browser will perform optimizations before animation. This will reduce the load on the GPU, and the animation will work smoother, without jerks.transform

Theoretically, you can use a decorator at a mouse event. But most often you will have unwanted jerks during the transformation. Therefore, it is worth setting the delay with caution. throttle

Conclusion

We've shown simple ways to create parallax in CSS and JS, but sometimes more complex effects are needed. For such cases, there are special libraries, for example, Loco Scroll, parallax JS or rellax. With their help, you can control the direction of movement of elements, fix slides when scrolling, create a "rush" or "delay" effect of animation.

And if you want to learn how to make animations of different levels of complexity, sign up for our course. You will learn how to animate a slider, a burger menu and an accordion. Learn how to create product cards with 3D effect, multi-layer parallax and parallax hats. And you will also learn how to optimize animations so that the site works faster.