HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Parallax animation, running well on Chrome but slow on Safari

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
animationsafaributslowparallaxrunningchromewell

Problem

I have the following script to create a parallax effect on the page using request animation frame, it runs well on Chrome, but on Safari is lagging, I think because I run too many calculations on each rAF.

$('.parallax').each(function () {
    var section = $(this),
        elements = section.find('[data-parallax-ratio]');

    elements.data('parallax-current', 0);

    function animation() {
      var scrollPos = $(window).scrollTop(),
          offsetY = section.offset().top - 60;

      for (var i = 0; i < elements.length; i++) {
        var el = $(elements[i]),
        ratio = el.data('parallax-ratio'),
        current = el.data('parallax-current');

        var newPos = (offsetY - scrollPos) * ratio;

        var pos = current - ((current - newPos) * 0.08);

        el.css('transform', 'translateY(' + pos + 'px)');

        el.data('parallax-current', pos);
      }

      window.requestAnimationFrame(animation);
    }

    animation();

  });

Solution

The major issue that I see in your code is that you keep requestAnimationFrame no matter what. This obviously causes the browser to update the css of the element at any time.

Since your animation is based on scroll I would suggest to define an handler to window.onscroll.

While doing this you perhaps may wish to transform your animation method to be a plugin method that you would call for every parallax element.

Resulting code becomes something like this:

$.fn.parallaxAnimate = function() {
    var elements = this.find('[data-parallax-ratio]');

    var scrollPos = $(window).scrollTop(),
      offsetY = this.offset().top - 60;

    for (var i = 0; i < elements.length; i++) {
        var el = $(elements[i]),
            ratio = el.data('parallax-ratio'),
            current = el.data('parallax-current') || 0;

        var newPos = (offsetY - scrollPos) * ratio;
        var pos = current - ((current - newPos) * 0.08);

        el.css('transform', 'translateY(' + pos + 'px)');
        el.data('parallax-current', pos);
    }
}


window.onscroll = function(){
$('.parallax').parallaxAnimate();
};

See a (ugly) fiddle

Edit: I was wrong on my previous attempt to solve smoothness and I was also wrong about subscribing onscroll

I tried to solve the problem that the OP is having as the animation is not smooth. On my previous attempt I tried to implement a scroll animation on a scroll event, and I got a warning on firefox (this is a free translation since I got my browser on portuguese unfortunately):


This site appears to use a scroll-linked position effect. This may not
work properly with asynchronous panning. See
https://developers.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects
for more details...

And there you can read an explanation of what are the causes of this warning.

Meanwhile, on this same documentation page on Other effects, you can have a link to a useful site that implements many scroll effects, one of those being the parallax. All of this only with css...

I will post here one smaller example based on the implementation of the site, give props to that man please, not to me ;).





/* Parallax base styles
--------------------------------------------- */
.parallax {
height: 500px; / fallback for older browsers /
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
-webkit-perspective: 300px;
perspective: 300px;
}

.parallax__group {
position: relative;
height: 500px; / fallback for older browsers /
height: 100vh;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
}

.parallax__layer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}

.parallax__layer--fore {
-webkit-transform: translateZ(90px) scale(.7);
transform: translateZ(90px) scale(.7);
z-index: 1;
}

.parallax__layer--base {
-webkit-transform: translateZ(0);
transform: translateZ(0);
z-index: 4;
}

.parallax__layer--back {
-webkit-transform: translateZ(-300px) scale(2);
transform: translateZ(-300px) scale(2);
z-index: 3;
}

.parallax__layer--deep {
-webkit-transform: translateZ(-600px) scale(3);
transform: translateZ(-600px) scale(3);
z-index: 2;
}

.parallax {
font-size: 200%;
}

/ centre the content in the parallax layers /
.title {
text-align: center;
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}

#group1 {
z-index: 5; / slide over group 2 /
}
#group1 .parallax__layer--base {
background: rgb(102,204,102);
}

#group2 {
z-index: 3; / slide under groups 1 and 3 /
}
#group2 .parallax__layer--back {
background: rgb(123,210,102);
}



Base Layer




Base Layer


Background Layer


Code Snippets

$.fn.parallaxAnimate = function() {
    var elements = this.find('[data-parallax-ratio]');

    var scrollPos = $(window).scrollTop(),
      offsetY = this.offset().top - 60;

    for (var i = 0; i < elements.length; i++) {
        var el = $(elements[i]),
            ratio = el.data('parallax-ratio'),
            current = el.data('parallax-current') || 0;

        var newPos = (offsetY - scrollPos) * ratio;
        var pos = current - ((current - newPos) * 0.08);

        el.css('transform', 'translateY(' + pos + 'px)');
        el.data('parallax-current', pos);
    }
}

Context

StackExchange Code Review Q#129173, answer score: 6

Revisions (0)

No revisions yet.