css-parallax

When 2 or more layers are moving at different speeds in the same or opposite direction it creates a sort of gliding effect. This effect is known as parallax. There are many ways we can achieve the parallax effect using CSS. There are many libraries available to do this task but in this article, we will understand how to create parallax using only CSS. (well most part of it)

For simplicity, we'll understand CSS only parallax first and we'll add some enhancements with little javascript. To give you an idea of what we're going to accomplish, here is a finished product that we'll have at the end of the article. It's 90% CSS and 10% javascript to trigger some transitions. I have an article that explains how to trigger animation by adding a class that uses the same fundamentals.

Pure CSS Parallax using perspective:

Let's try to understand CSS parallax step by step. These steps contain some blocks of code for understanding. CSS code blocks containing rules that are needed.

Step 1: Secret scrollable sections

The basic structure will have a parent container element that will scroll all of its child sections.

The HTML looks like this.

<div class="parent">
<section class="exso"></section>
<section class="thermo"></section>
<section class="meso"></section>
<section class="strato"></section>
<section class="tropo"></section>
<section class="base"></section>
</div>

CSS part will have a perspective property applied on the parent so inside elements can scroll. Inside sections have basic properties like height, overflow, and transform-style.

.parent{ overflow-y: auto;height:100vh;perspective: 300px;}
section{ height: 150vh; transform-style: preserve-3d;}
.parent:before{ content:'';background:linear-gradient(0deg,#96B6CB,#96B6CB,#73a2c2,#407da7,#153e58,#0a2638), url(sky.jpg) no-repeat0-200px/ 100%;background-blend-mode: screen;
position: absolute; top:0; left: 0; width:100vw; height: 900vh;}

To separate each section we'll use different colors and images in the section. The classes

Base, tropo, stato, meso, thermo and exso adds basic styling to the sections so they could be differentiated.

At the end of this step, you'll empty scrollable space.

Now let's move towards adding backgrounds.

Step 2: Add Background Elements

We'll add different backgrounds on all ther sections using absolutely positioned span element.

<div class="parent">
<section class="exso"><span></span></section>
<section class="thermo"><span></span></section>
<section class="meso"><span></span></section>
<section class="strato"> <span></span> </section>
<section class="tropo"><span></span></section>
<section class="base"><span></span></section>
</div>

CSS skeleton at this point will consist of just backgrounds. And we'll add transform:translateZ(value) to move the layer back from the viewer.

section span{
position: absolute;
left:0;
top:0;
width:100%;
height:100%;
display: block;
}
.base{
background: ...;
}
.base span{
background: ...;
transform: translateZ(-100px);
}
section.tropo span{
background:...;
transform: translateZ(-300px);
}
section.strato span{
background: ...;
transform: translateZ(-300px);
}
section.meso span{
background:...;
transform: translateZ(-600px);
}
section.thermo span{
background:...;
transform: translateZ(-200px) ;
}
section.exso span{
background:...;
transform: translateZ(-1200px);
}

At the end of this step, you'll end up with something like moving layers. These layers move at different speeds when you scroll. Now we need to fix the scale of that section moved back.

This happens because the object moved away from the viewer so objects farthest will appear smaller than those with no translateZ applied. The next step is to fix the scale.

Step 3: Fix the Scale of the layers

To trigger different speeds we need to do some math to move a layer in the background or foreground and scale it. So we need to determine the scaling factor. The equation for scaling factor is:

scale(min) = 1 + (translateZ * -1) / perspective;

How to use scaling factor?

For example, if we're using perspective = 300px and we want to move the layer backward by 100px, then we have to scale that layer by

scale(min) = 1+ (100-1)/300 = 1.33 ~1.4

So we have to apply a scale of 1.4 to the layer using translateZ(100px) to match the size of un-translated layers.

At the end of this step, we'll add scale to the transform and layers will mostly look equally scaled.

The skeleton CSS will look something like this: Everything stays the same except the scale function added.

.base span{
background: ...;
transform: translateZ(-100px) scale(1.2);
}
section.tropo span{
background:...;
transform: translateZ(-300px) scale(3);
}
section.strato span{
background: ...;
transform: translateZ(-300px) scale(2);
}
section.meso span{
background:...;
transform: translateZ(-600px) scale(2);
}
section.thermo span{
background:...;
transform: translateZ(-200px) scale(2);
}
section.exso span{
background:...;
transform: translateZ(-1200px) scale(5.2);
}

Once the scaling factor is added, we start to see some parallax in action.

Step 4: Add foreground layers

We can add some more children to the section using the same principle. We add elements to the layer, translate that back or forth, add a scaling factor.

In our example , the html will look like this:

<div class="parent">
<section class="exso"><span></span><div></div></section>
<section class="thermo"><span></span> <div></div> </section>
<section class="meso"><span></span><div></div> </section>
<section class="strato"> <span></span> <div></div> </section>
<section class="tropo"><span></span><div></div></section>
<section class="base"><span></span><div></div></section>
</div>

We'll add some moving element using newly added <div> element. We'll use different depts and see the effect. The css skeleton for adding new layers look lik this:

section div{position: absolute; left:0; right:0; top:20%; bottom:20%;
height:100%;display: block;}
section.tropo div{
background: ...;
transform: translateZ(-500px) scale(3.5);
}
section.strato div{
background: ...;
transform: translateZ(-100px) scale(1.5);}
section.meso div{
background: ...;
transform: translateZ(-300px) scale(1.5);
}
section.thermo div{
background: ...;
transform: translateZ(-160px) scale(1.6);
}
section.exso div{
background: ...;
transform: translateZ(-600px) scale(1.5);
}

At the end of this step, we'll have a couple of objects parallax when we scroll and this is using only CSS.

This is the end of CSS-only parallax. But we'll add some javascript to enhance the effect.

Step 5: Javascript enhancement

We mostly completed how to create parallax scrolling sections with pure CSS. There is one more step we can do using javascript. We'll try to add a cool spaceship fixed over the section. So it will feel like the spaceship is traveling through different layers of the atmosphere. If you're not a javascript fan, in near future you'll come across a technique which will enable you to animate your element on scroll without using javascript but for now, let's use simple js right now.

So this layer will be outside the scroll container so it can be fixed in one place. HTML structure will have one more element now.


<span class="starship"></span>
<div class="parent">
<section class="exso"><span></span><div></div></section>
<section class="thermo"><span></span> <div></div> </section>
<section class="meso"><span></span><div></div> </section>
<section class="strato"> <span></span> <div></div> </section>
<section class="tropo"><span></span><div></div></section>
<section class="base"><span></span><div></div></section>
</div>

We have just added one dom element in the old example.

We'll create CSS and some states of the same element to animate it while scrolling.

.starship{
pointer-events: none;
z-index:10;
transform: rotate(90deg);
transition: transform 500ms ease-in-out;
position: fixed;
}
.wiggle{
transform: rotate(0deg);
transition-duration: 3s;
}
.hover,.land{
transform: rotate(0deg);
}

These classes rotate the spaceship from 90deg to 0deg when the class is applied.

We'll add a small javascript to determine scroll percentage and add/change classes based on how much scroll has happened.

function getScrollPercent(elm) {
h = elm.scrollHeight;
st = elm.scrollTop;
oh = elm.offsetHeight;
perc = ( st + oh)*100/h;
return perc;
}
document.querySelector('.parent').addEventListener('scroll',function(e){
p = getScrollPercent(document.querySelector('.parent'));
if(p>20){
document.querySelector('.starship').classList = ["starship bf"];
}
if(p>30){
document.querySelector('.starship').classList = ["starship wiggle"];
}
if(p>80){
document.querySelector('.starship').classList = ["starship hover"];
}
if(p>99){
document.querySelector('.starship').classList = ["starship land"];
}
});

The finished example looks like this:

Scroll up and down to see parallax between different layers.

Pure CSS parallax Limitation:

Since we're not able to manipulate the layer movement, we can only make it slower or faster, we'll end up with all the elements moving in one direction. We can't move elements in opposite directions. This effect works just in one direction.

Browser compatibility can be a problem. This effect may not work on some browsers. Everyone knows which one, so I won't tell you the name.

Using CSS Parallax In Real Projects:

CSS parallax is a cool hack that uses basic CSS transform properties to create speed differences between layers. This could be an easy hack for someone who is looking for quick and dirty parallax. For more control over moving elements, we have to use javascript. This demo doesn't use javascript much. But I have an article that explains how to animate on scroll. Personally, I use the method a lot. This gives me quick parallax for my micro-interactions so why not?

Leave a Reply

Your email address will not be published. Required fields are marked *