Case study: create a parallax effect directly on <img> tags with JavaScript
Tags: javascript, css, ux
Published: 2019-05-29 13:26:19

> Parallax effects in web work only with the CSS background-image property, why is
> that? **This is not really practical.**

This was a starting point for me. I wanted to have a nice parallax effect to apply directly on image tags, for several reasons:

* It is the most natural way to use images on the web
* *background-image* property doesn’t support the equivalent of picture tag,
*srcset* and *sizes* attributes easily
* *background-image* with CMS is not optimal

The fact that I couldn’t find this kind of library/plugin anywhere was
surprising, so** I decided to create a new one**.

### Main objective

I wanted to be able to add parallax effects without any change on HTML or CSS.

The reason being I already had a website almost finished, and I didn’t see myself changing all my *< img>* tags to *< div>* with *background-image*.

This was the main thread of this library, I wanted to have a very simple way to apply parallax on any website already on production without any rework. And as a bonus, a very smooth and natural animation feeling — the effect should only give a plus and not cost anything else.

### 1st Issue: How to not break the layout?

![](https://thepracticaldev.s3.amazonaws.com/i/gpa5y29zkcto3j3naanv.gif)

The first concern was to manage the transition of the image without breaking the layout. Usual parallax effects are located in a very specific area created for that purpose only, the main thread of this case is the opposite.

Parallax should be easily added anywhere there is an image, even if its located between two text blocs. And you don’t want the image to be transitioned anywhere on the website and potentially overlap with content.

#### Solution

I have therefore reached the conclusion to dynamically add a container as a parent of the image. This container will have the same dimensions as the image and a hidden overflow. Now the image can translate from an infinite number of pixels without breaking the layout.

    <img src="image.jpg" alt="image" />

will become:

    <div style="overflow: hidden">
        <img src="image.jpg" alt="image" />
    </div>

### 2nd Issue: How to avoid blank spaces?

![](https://thepracticaldev.s3.amazonaws.com/i/thipgev0b1h2h504eo1d.gif)

So a new problem appeared, blank spaces when the image reaches its physical limit.

This was very problematic, considering that the whole point was to leave the initial layout of the page unaltered. Not to mention the smooth and natural animation initially planned.

#### Solution

I have opted for this solution: add a scaling transformation on the image.
Meaning the image will have more matter to be transitioned with. This range can be easily calculated by:

    (imageHeight * scale - imageHeight) = range

For example, if the image is 500px height, and we apply a 1.5 scale, that means the image will have a 250px range to translate on.

Now we need to get the percentage of the image position comparing to the
viewport, using a more laborious calculation:

    ((viewportBottom - imageTop) / ((viewportHeight + imageHeight) / 100)) = percentage

And finally transcript this percentage into the range:

    ((percentage / 100) * range - range / 2) = translation

So **the translation can be applied gradually** on the image using the
*transform: translate(translation);* property.

#### Cons: quality of the image

Theoretically, we can apprehend the fact that if the scale is applied to an image, we will lose quality.

In practice, **this is hardly noticeable** if the scale is set at 1.3 (which is the default value of the library). And even less if you cater for this by adding an image with a bigger size — meaning if your image is 500px and you want to apply a 1.5 scale, compensate by using a 750px width image.

### Final render

![](https://thepracticaldev.s3.amazonaws.com/i/i1vfwvwaao4nizzwl13u.gif)

### Performances

With parallax animations, comes **performance warning**. Thanks to [Paul
Irish](https://www.paulirish.com/) and [html5rocks](https://www.html5rocks.com/), a lot of answers were already provided and explained to an extent. Still, there is a lot to do here, some examples:

* The scroll event is performance greedy, so the use of *Request Animation Frame* is highly recommended. I shall not elaborate, as this subject has already been touched upon [by others](https://css-tricks.com/using-requestanimationframe/).
* *Intersection Observer API* is really powerful and performance light to check which elements are visible in the viewport. Because there is no need to cater for images that are not in the current viewport.
* A key point is to reduce [the
reflow](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) of the browser as much as possible, one (of many) solution to this is to reduce as much as possible to fetch the viewport and element offsets.
* *CSS Hardware Acceleration* isn’t very well-know but yet powerful. Changing the *transform: translateX();* to *transform: translate3D();* will leverage the GPU power and offers [better
performance](https://blog.teamtreehouse.com/increase-your-sites-performance-with-hardware-accelerated-css).

**Performances are constant challenges** against what has been done. And improve
what can be improved thanks to new technologies or wrong initial implementation.

*****

This case study and this library **only reflect my point of view**, which may
not be the best. You are most welcome to challenge, debate or argue any of the
things I said above.

Last but not least, you can check the simpleParallax library on
[simpleparallax.com](https://simpleparallax.com/) and [github](https://github.com/geosigno/simpleParallax).