Search code examples
javascripthtmlcssloopsparallax

Looping parallax effect on full-width element keeps jumping in CSS / JS


My preferred end goal is to have a performance friendly background that smoothly loops without jumping. All the resources that I have found online either are not very performance friendly or they only work with elements that have a set width.

Currently everything looks okay, but the background scales poorly on different screens, and will make large jumps occasionally. I assume the jumping is due to an error in the translation of the elements, but I haven't yet seen a good solution. Setting the width of the elements to 200% and translating them over -50% seems like a hacky solution, and I feel as if there should be a much better way of doing it.

I would prefer to find an all CSS solution, but if nothing else is feasible, resorting to JS is fine.

Fiddle: https://jsfiddle.net/r4fz0Lot/3/

Code:

* { box-sizing: border-box; }
html, body, #container { width: 100%; height: 100%; }
body { margin: 0; }

#container {
  background-color: rgba(0, 0, 0, 0.9);
  image-rendering: pixelated;
  overflow: hidden;
  position: fixed;
}

#stars {
  background: url('https://i.imgur.com/Ym03Zkf.png') repeat 0 0;
  animation: loop 25s linear infinite;
  z-index: 1;
}

#mountains {
  background: url('https://i.imgur.com/jfef1r3.png') repeat-x 0 bottom;
  animation: loop 20s linear infinite;
  z-index: 2;
}

#ground {
  background: url('https://i.imgur.com/P13CzUo.png') repeat-x 0 bottom;
  animation: loop 15s linear infinite;
  z-index: 3;
}

#stars, #mountains, #ground {
  width: 200%; height: 100%;
  background-size: 30%;
  bottom: 0; left: 0;
  position: fixed;
}

@keyframes loop {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
<div id="container">
  <div id="ground"></div>
  <div id="mountains"></div>
  <div id="stars"></div>
</div>


Solution

  • You set background-size to 30% so you need to translate some multiple of 30% to translate "one image unit"

    * { box-sizing: border-box; }
    html, body, #container { width: 100%; height: 100%; }
    body { margin: 0; }
    
    #container {
      background-color: rgba(0, 0, 0, 0.9);
      image-rendering: pixelated;
      overflow: hidden;
      position: fixed;
    }
    
    #stars {
      background: url('https://i.imgur.com/Ym03Zkf.png') repeat 0 0;
      animation: loop 8s linear infinite;
      z-index: 1;
    }
    
    #mountains {
      background: url('https://i.imgur.com/jfef1r3.png') repeat-x 0 bottom;
      animation: loop 6s linear infinite;
      z-index: 2;
    }
    
    #ground {
      background: url('https://i.imgur.com/P13CzUo.png') repeat-x 0 bottom;
      animation: loop 5s linear infinite;
      z-index: 3;
    }
    
    #stars, #mountains, #ground {
      width: 200%; height: 100%;
      background-size: 30%;
      bottom: 0; left: 0;
      position: fixed;
    }
    
    @keyframes loop {
      from { transform: translateX(0); }
      to { transform: translateX(-30%); }
    }
    <div id="container">
      <div id="ground"></div>
      <div id="mountains"></div>
      <div id="stars"></div>
    </div>