1

On experimenting with parallax scrolling using css perspective, transformZ, and preserve-3d, parallax effects can be applied to scrolling containers using hardware acceleration with near perfect performance.

eg:

.parallax {
  perspective: 1px;
  height: 100vh;
  overflow-x: hidden;
  overflow-y: auto;
  transform-style: preserve-3d;
}
// any containing html tag in the middle
.is-container {
   transform-style: preserve-3d;
}

.parallax__layer--base {
  transform: translateZ(0);
}
.parallax__layer--back {
  transform: translateZ(-1px);
}

On further testing, trying to apply this logic to the main scrolling element (body, html, root) it is proving quite impossible to get full browser support.

Applying this to the equivalent styles of .parallax to the <HTML> tag and applying the equivalent of .is-container to body will break the effect on Chrome and Safari.

Applying th equivalent styles of .parallax to the <BODY> tag will preserve the effect so long as HTML is set to overflow: hidden. Whilst this might seem like a workable approach it then breaks native scroll implementations in browsers. For example, Apple Mobile Safari with the address bar at the bottom runs an animation based on user scroll to hide the address bar as the user scrolls down. This will not take place with BODY acting as the scroll element. Apple Desktop Safari equally floods the background of the highest most positioned item into the app chrome. This will also be broken by this approach.

The google demo https://developer.chrome.com/blog/performant-parallaxing for example also is not using the root / html of page and therefore disables this behaviour of the Apple Safari browsers.

I have tried every combination of applying the equivalent styles of .parallax to :root, HTML and Body but have not been able to rectify this problem. Am I missing something or is this as yet not achievable.

You obviously won't be able to see the issue if we use a wrapper like jsFiddle or CodePen.

Here is some code if you want to reproduce it.

<html>
<style type="text/css">
   :root {
    height: 100vh !important;
    overflow-x: hidden;
    overflow-y: auto;
    perspective: 1px;
    transform-style: preserve-3d;
  }
  
  body,
  html {
    transform-style: preserve-3d;
  }
  
  .test {
    transform: translate3d(0, 0, -2px);
    transform-style: preserve-3d;
  }
  
  h1 {
    font-size: 120px;
    padding: 60px;
    text-align: center;
  }
</style>

<body>
  <h1>HI 1</h1>
  <h1>HI 2</h1>
  <h1 class="test">HI 3</h1>
  <h1>HI 4</h1>
  <h1>HI 5</h1>
  <h1>HI 6</h1>
  <h1>HI 7</h1>
  <h1>HI 8</h1>
  <h1>HI 9</h1>
  <h1>HI 10</h1>
</body>

</html>

As you may see, the perspective element is maintained but the parallax effect is lost.

If we do this however, the parallax is maintained but we broke native listeners to the root scroll element such as Apple Mobile Safari.

<style type="text/css">
   :root {
    height: 100vh !important;
    overflow: hidden;
  }
  
  body {
    height: 100vh !important;
    overflow-x: hidden;
    overflow-y: auto;
    perspective: 1px;
    transform-style: preserve-3d;
  }
  
  body,
  html {
    transform-style: preserve-3d;
  }
  
  .test {
    transform: translate3d(0, 0, -2px);
    transform-style: preserve-3d;
  }
  
  h1 {
    font-size: 120px;
    padding: 60px;
    text-align: center;
  }
</style>

Update 6 February 2024

// NOT THE ANSWER -- BUT AN ALTERNATIVE //

This is a shortcoming of the capabilities inside css as incorporated by browsers at this moment in time and is likely to remain so for browser compatibility reasons. The only way is to override what is the scrolling element "viewport" to an element we can control. For most browsers this is syphoned up from the html selector and can be accessed there or via the :root selector.

Whilst perspective is taken into account by html, the parallax effect will not work. Translate Z will but something about the way this works flattens the document after again. No amount of preserve-3d applied to html, body and all subsequent containers will change this. The effect of translateZ will work but the parallax effect of it will not.

Therefore the solution is to play with translateY via javascript to achieve the objective. Most parallax libraries are expensive in terms of performance, something that becomes all to evident when used on mobile.

However there are ways to refine this process. Ensuring that effects only take place when visible, ensuring that effects target css 3 transformations not position arguments and ensuring that calls are controlled via an interval on the scroll listener (edit below) are paramount. Making such a script genuinely usable also requires some effort. How do you determine what the zero point is for an element in the scroll view, how much its position can be altered below this point and above this point, it's speed, direction etc..

To solve all of these problems I started with a light weight javascript named mini parallax. https://www.cssscript.com/demo/mini-horizontal-vertical-parallax/.

It became clear that it relied on knowing the genuine offset of any element to determine the zero point. I overcame this by establishing the zero point as the exact middle point of any element when in the middle of the viewport unless that number was less than 0 in which case 0 or greater than the document height in which case the document height.

I removed most of it's unnecessary helpers such as scaling, calculating etc.. And finally rebuilt it entirely. Minified it is less than 500 bytes. Performance in browser is perfect and in mobile it averages 28 FPS with minor fluctuations occasionally. Not perfect but hardly noticeable.

Furthermore that zero point can be modified in multiples of the window height (vh) via data-* properties as can the speed and the minimum and maximum range all in multiples of vh assuming 0 is the centre of the element when visibly at the centre of the screen.

Any html element with .parallax will be included. Additional properties are controlled via data-* properties.

data-parallax-origin: // default 0 (centre of the element when that element is in centre of the visible screen, eg. 0.5 would be centre of the element when three-quarters of the way down the screen.

data-parallax-speed: // default 0.25 (the element scrolls .25 faster that the scroll view)

data-parallax-max: // default 1 (when the effect starts happening, eg. 1 means when the top of the element has reached the bottom of the scrollable window).

data-parallax-min: // default -1 (when the effect stops happening).

data-parallax-axis: // default "y" (when set to x elements would transform in the horizontal axis as the window scrolled in the vertical axis).

Any element can be nested inside any other element and there are no limitations other than performance.

I have not found a single parallax library more efficient than this in terms of performance that is publicly available.

I will therefore publish it shortly along with 12 other scripts and modifiers that answer most of the questions I have asked of late on StackOverFlow.

// NOT THE ANSWER -- BUT AN ALTERNATIVE (Improved) //

Don't use an interval. Use requestAnimationFrame. Contrary to another answer written on a different post about how this has no effect on css transform properties, they are a bit a prat. If dealing with a transition they would be correct but not a transform.

requestAnimationFrame(function(){ // stuff // });

.. renders only when a the browser intends to paint the result of the layout.

Therefore:

  1. Calculate everything you need to calculate on DOM load. Calculate it again if the screenSize changes and store all of this information in a variable (data-• properties if you must).

  2. Run a function on scroll that measures where an element should transform to but don't set it, just store it in a variable.

  3. If any element in your variable is different than the value it should now be, store them all, id's to them all or better still indices to them all in anotherVariable.

  4. in that anotherVariable is not empty

    requestAnimationFrame(function(){ // and update the transform property of all affected elements }

This will work out the position before paining. It's not perfect because scrollListener is hefty but it is better than an interval that also got overloaded with and inferred a delayed value.

QED :: code will come later.

3
  • 1
    I appreciate that I have exposed a problem that almost no one cares about (not for the first time). I have implemented 16 separate parallax javascript libraries and found something that with a few modifications can be manipulated to get close enough not to notice the choppiness inherent with catching scroll events. It is simply a shame that whilst this is all possible exclusively with pure css, this oversight remains. Commented Feb 4, 2024 at 20:21
  • What's the disadvantage to wrapping it in a full-sized DIV under BODY? Commented Sep 25, 2024 at 16:59
  • @crackers When viewed through a mobile browser certain parts of the browser frame are supposed to contract as the window scroll down. This would be disabled keeping the toolbar and address bar permanently visible. On desktop browsers with bouncing overscroll effect, the behaviour would be maintained on some (Safari) and disabled on others (Chrome). Commented Sep 27, 2024 at 9:42

1 Answer 1

0

Not pure CSS, but I think it's pretty lightweight and easy to use. Have a look:

$(document).ready(function() {
  // The function
  var background_image_parallax = function($object, multiplier) {
    multiplier = typeof multiplier !== 'undefined' ? multiplier : 0.5;
    multiplier = 1 - multiplier;
    var $doc = $(document);
    $object.css({
      "background-attatchment": "fixed"
    });
    $(window).scroll(function() {
      var from_top = $doc.scrollTop(),
        bg_css = '0px ' + (multiplier * from_top) + 'px';
      $object.css({
        "background-position": bg_css
      });
    });
  };

  //Just pass the jQuery object
  background_image_parallax($(".box1"));

  //optional second value for speed
  background_image_parallax($(".box3"), 0.25);

});
#page {
  margin: 0;
  padding: 0;
  display: block;
  background-image: url("https://source.unsplash.com/L0jjZqsZVpw/960x700");
  background-color: #000;
  background-size: cover;
  min-height: 100%;
  color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div id="page" class="box1">
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Elementum nisi quis eleifend quam. Diam ut venenatis tellus in metus. Sit amet mauris commodo quis imperdiet massa tincidunt.
    Ultrices dui sapien eget mi proin sed libero enim. Tempor orci dapibus ultrices in iaculis nunc sed. Accumsan tortor posuere ac ut consequat semper. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Eget est lorem ipsum dolor sit. Nunc
    sed velit dignissim sodales ut eu.
  </p>
  <p>
    Augue interdum velit euismod in pellentesque massa placerat duis ultricies. Quis eleifend quam adipiscing vitae proin sagittis nisl. Quam quisque id diam vel quam elementum. Placerat in egestas erat imperdiet. Amet justo donec enim diam vulputate ut pharetra
    sit. Nunc sed velit dignissim sodales. Arcu dui vivamus arcu felis bibendum ut tristique. Vitae tempus quam pellentesque nec. Morbi tincidunt augue interdum velit euismod. Morbi tristique senectus et netus et malesuada. Ipsum dolor sit amet consectetur
    adipiscing elit ut aliquam. Eu turpis egestas pretium aenean. Ultricies leo integer malesuada nunc vel risus commodo viverra maecenas. Ut faucibus pulvinar elementum integer enim neque volutpat ac.
  </p>
  <p>
    Quisque non tellus orci ac auctor augue mauris augue. Vitae tempus quam pellentesque nec nam aliquam sem. Ut pharetra sit amet aliquam id diam maecenas ultricies mi. Ac tortor dignissim convallis aenean et. Hendrerit dolor magna eget est. Tempus urna
    et pharetra pharetra. Mauris sit amet massa vitae tortor condimentum. Leo vel orci porta non pulvinar neque laoreet suspendisse interdum. Leo vel orci porta non pulvinar neque laoreet. Et malesuada fames ac turpis egestas maecenas pharetra convallis
    posuere. Diam volutpat commodo sed egestas. In metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Lectus arcu bibendum at varius. Odio morbi quis commodo odio. Congue eu consequat ac felis donec et odio. Porttitor rhoncus dolor purus
    non enim praesent. Nunc id cursus metus aliquam eleifend. Libero nunc consequat interdum varius sit amet mattis vulputate. Turpis nunc eget lorem dolor sed.
  </p>
  <p>
    Sed vulputate mi sit amet. Placerat in egestas erat imperdiet sed euismod nisi porta lorem. Odio pellentesque diam volutpat commodo. Nunc sed id semper risus in hendrerit gravida rutrum quisque. Aenean et tortor at risus viverra adipiscing. Nulla pharetra
    diam sit amet nisl suscipit adipiscing. Odio ut enim blandit volutpat maecenas. Lorem ipsum dolor sit amet consectetur. Sed augue lacus viverra vitae congue eu consequat ac. Pharetra massa massa ultricies mi quis hendrerit dolor. Pharetra et ultrices
    neque ornare aenean euismod elementum. Porttitor lacus luctus accumsan tortor posuere ac. Enim eu turpis egestas pretium aenean pharetra magna. Accumsan lacus vel facilisis volutpat est velit. Habitant morbi tristique senectus et netus et. Sit amet
    est placerat in egestas erat imperdiet sed. In hac habitasse platea dictumst quisque.
  </p>
  <p>
    Suspendisse ultrices gravida dictum fusce ut placerat orci. Non diam phasellus vestibulum lorem sed risus ultricies tristique. Augue eget arcu dictum varius duis at consectetur lorem. Aliquam etiam erat velit scelerisque in dictum non consectetur. Dolor
    sit amet consectetur adipiscing elit ut aliquam. Ut venenatis tellus in metus vulputate eu scelerisque felis. Vitae nunc sed velit dignissim sodales ut eu sem. Pulvinar etiam non quam lacus suspendisse faucibus. Magna fringilla urna porttitor rhoncus
    dolor purus non. A diam maecenas sed enim ut. Mauris cursus mattis molestie a iaculis at erat. Nisi est sit amet facilisis magna etiam tempor orci eu. Gravida rutrum quisque non tellus orci ac auctor. Et pharetra pharetra massa massa ultricies mi
    quis hendrerit dolor. Cursus turpis massa tincidunt dui ut ornare lectus sit amet. Vitae turpis massa sed elementum tempus egestas sed sed. Enim nec dui nunc mattis enim ut tellus elementum. Porttitor leo a diam sollicitudin tempor. Euismod quis viverra
    nibh cras.
  </p>
  <p>
    Diam maecenas ultricies mi eget mauris pharetra et. Donec massa sapien faucibus et molestie ac. Eu consequat ac felis donec. Magna fringilla urna porttitor rhoncus dolor purus non. Risus pretium quam vulputate dignissim suspendisse. Sed euismod nisi porta
    lorem mollis aliquam ut porttitor leo. Vitae proin sagittis nisl rhoncus mattis rhoncus urna neque. Luctus venenatis lectus magna fringilla urna porttitor rhoncus dolor purus. Porta non pulvinar neque laoreet suspendisse interdum consectetur libero.
    Lacus vel facilisis volutpat est velit egestas. Ut faucibus pulvinar elementum integer enim. Lorem sed risus ultricies tristique. Lobortis feugiat vivamus at augue eget arcu. Accumsan in nisl nisi scelerisque eu ultrices vitae auctor eu. At in tellus
    integer feugiat scelerisque varius morbi. Donec pretium vulputate sapien nec sagittis. Tincidunt praesent semper feugiat nibh. Massa tempor nec feugiat nisl pretium fusce. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Dui id ornare
    arcu odio. Non enim praesent elementum facilisis. Tellus molestie nunc non blandit massa enim nec. Aenean vel elit scelerisque mauris. Sed faucibus turpis in eu mi bibendum neque egestas. Sit amet venenatis urna cursus eget nunc scelerisque viverra
    mauris. Integer vitae justo eget magna. Enim nunc faucibus a pellentesque sit amet porttitor eget.
  </p>
  <p>
    Amet luctus venenatis lectus magna fringilla. Orci porta non pulvinar neque laoreet. Quis eleifend quam adipiscing vitae proin sagittis nisl rhoncus. Blandit massa enim nec dui. Netus et malesuada fames ac turpis. Erat imperdiet sed euismod nisi porta
    lorem mollis. Pharetra magna ac placerat vestibulum lectus mauris. Luctus venenatis lectus magna fringilla urna porttitor rhoncus dolor. Pretium quam vulputate dignissim suspendisse. Velit laoreet id donec ultrices. Arcu risus quis varius quam. Egestas
    maecenas pharetra convallis posuere morbi leo urna molestie. Erat nam at lectus urna duis convallis convallis tellus. Non arcu risus quis varius quam quisque id diam vel. Tempus egestas sed sed risus pretium quam vulputate dignissim.
  </p>
  <p>
    Amet tellus cras adipiscing enim eu turpis egestas pretium aenean. Arcu risus quis varius quam. Aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis. Mi in nulla posuere sollicitudin aliquam ultrices sagittis orci. Erat pellentesque
    adipiscing commodo elit at imperdiet dui accumsan. Amet justo donec enim diam vulputate ut pharetra sit. Neque ornare aenean euismod elementum nisi. Lobortis feugiat vivamus at augue eget. Rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt
    lobortis. Neque convallis a cras semper auctor neque vitae tempus. Ut faucibus pulvinar elementum integer enim neque volutpat.
  </p>
  <p>
    At quis risus sed vulputate odio ut. Purus gravida quis blandit turpis cursus. Mattis enim ut tellus elementum sagittis vitae et leo duis. Quis varius quam quisque id diam vel. Nec dui nunc mattis enim ut tellus elementum sagittis. Fermentum odio eu feugiat
    pretium nibh. Nunc id cursus metus aliquam eleifend mi in nulla posuere. Posuere lorem ipsum dolor sit amet consectetur. Tempor commodo ullamcorper a lacus vestibulum sed. Vel pharetra vel turpis nunc eget lorem. Ipsum a arcu cursus vitae congue mauris
    rhoncus aenean. Ipsum faucibus vitae aliquet nec. Nunc congue nisi vitae suscipit tellus mauris a diam maecenas. Donec ac odio tempor orci dapibus. Quam nulla porttitor massa id neque aliquam. Convallis posuere morbi leo urna molestie at elementum.
    Lectus proin nibh nisl condimentum id venenatis. Nulla facilisi morbi tempus iaculis.</p>
</div>

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you but No no no. 1: You are adjusting the background position. This could never be used for anything other than a background image which the example quite specifically doesn't use and the question does not reference. 2: You are using jQuery which is not lightweight at all. There is no way this code could ever be performative and no way it could be used outside its very limited scope.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.