Parallax with vanilla JS (and more!)

This short demo contains two simple things: darkening background images using pure CSS and a simple, easy to understand, vanilla JavaScript parallax.

Parallax Demo Screenshot

Posted November 30, 2018
Filed Under: blog

Let’s start by posing this problem: you’ve got a nice hero image with some very engaging text you’d like to lay over it. But wait! The background has dark and light colors in it, and no matter what, you can’t position the text in a way which produces consistent contrast. If only you could add a CSS filter() to the background image! Rats! Oh, and you want to add a couple of parallax effects to the mix!
Luckily, there’s a little way around it. By combining two possible values (linear-gradient and url()) for the background property, you can “darken” the image because of the way the background stacks.

div {
  background: linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url('/path/to/img.jpg');

You can also use this trick to “lighten” background images with dark text on them.
Check out this demo:

See the Pen “Darkening” a background-image with CSS by Nate Northway (@the_Northway) on CodePen.

Now, for the simple Parallax

The parallax effect describes one object (in this case, a background image) moving at a different rate than another object in the same field of view. This effect can create a sense of depth in a 2-dimensional environment. To achieve this effect on a background image, it needs to scroll at a different rate than the content surrounding it. We have to apply this effect only to containers that are in view, and we have to scroll the background image relative to its parent container. To get the desired background position offset, we must figure how much the user has scrolled since the element became visible, change that number by the rate we wish to scroll the background image, then apply that number to the background image top position as a pixel value, inverted if the number is greater than 0. If we don’t invert the number when it’s greater than 0, the background image’s offset from the top will be a positive value, or below the top of the parent element, which would mean empty space. Here’s that logic in a function

function parallax(targetElements) {
  targetElements.forEach(el => {
    if (inView(el)) {
      var scroll = window.pageYOffset;
      var elementPosition = el.offsetTop;
      var rate = 3;
      var calculation = (scroll - elementPosition) / 3;
      if (calculation > 0) {
        calculation = -calculation;
      } = "0% " + calculation + "px"; 

//super lean version
function parallax(targetElements) {
  targetElements.forEach(el => {
    if (inView(el)) {
      var scroll = (window.pageYOffset - el.offsetTop) / 3;
      var y = scroll > 0 ? -scroll:scroll; = "0% " + y + "px"; 

Of course, this only works with some CSS. The background image needs to be taller than it’s parent by at least 2 times and it’s initial starting position must have the top of the image be at the top of the parent container. We should probably also not repeat the background image. Here’s what that CSS will look like:

.parallax {
  /*Container height is nominal, but it should be fixed*/
  height: 50vh;
  background-image: url('path/to/image.jpg');

  /*The important stuff is here*/
    The height of the background must be twice the height of the container. 
    The width should be 'auto' to preserve aspect ratio. 
    These values might change depending on the aspect ratio of your chosen image. 
    If you see clipping, I suggest trying to flip these values to 100vw auto;
  background-size: auto 100vh;

  /*The position should be at the top*/
  background-position: 0% 0%;

  /*There should be no repeating of the background-image*/
  background-repeat: no-repeat;

Putting it all together:

See the Pen Parallax Demo by Nate Northway (@the_Northway) on CodePen.


Leave a Reply

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