bwad
bwad

Reputation: 81

Animations lagging on mobile chrome

I'm trying to do animations on my website. I'm using have a similar version of the jsfiddle code linked below. When viewed on desktop, the animations work well. However when viewed on mobile, specifically on my chrome browser, there is a weird lag. The jsfiddle shows the exact same lag when I open it on my phone. If I restart the chrome app the lag goes away back comes back soon after.

This issue doesn't occur in Safari.

I have the latest iPhone with IOS 14.6 and chrome V90.

https://jsfiddle.net/brodriguez98/e2bvwcja/33/

HTML:

<html>
 <p style = 'margin-top: 100vh;'>above</p>
 
 <img class = 'balltest show-on-scroll standard-push' src = 'http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png'/>
 
 <img class = 'balltest show-on-scroll fade-in' src = 'http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png'/>
  
 <p style = 'margin-bottom: 100vh'>below</p>
</html>

CSS:

.balltest {
    width: 50px;
}

.fade-in {
    opacity: 0;
    -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
    will-change: transform, opacity;
}

.standard-push {
    opacity: 0;
    transform: translateY(4em);
    -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out, translateZ(0);
    -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
    will-change: transform, opacity;
}

.is-visible {
    transform: translateY(0);
    opacity: 1;
}

Javascript:

var elementsToShow = document.querySelectorAll('.show-on-scroll');
$(window).scroll(function() {
    Array.prototype.forEach.call(elementsToShow, function (element) {
        if (isElementInViewport(element)) {
            element.classList.add('is-visible');
        } else {
            element.classList.remove('is-visible');
        }
    });
});


// Helper function from: http://stackoverflow.com/a/7557433/274826
function isElementInViewport(el) {
    // special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }
    var rect = el.getBoundingClientRect();
    return (
        (rect.top <= 0 &&
            rect.bottom >= 0) ||
        (rect.bottom >= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.top <= (window.innerHeight || document.documentElement.clientHeight)) ||
        (rect.top >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight))
    );
}

I apologize for the tiny screen, couldn't get JSfiddle on fullscreen on my phone:

Animation working right after restarting mobile chrome: https://www.loom.com/share/ac6c843b90d2428bb875572d55e32959

Animation breaking soon after (when I close/reload the page): https://www.loom.com/share/e51cf88aa1a74aed8e4d1ed253e83ea0

This is exactly the same behavior I'm seeing on my website using mobile chrome browser.

Update: Neither of the answers below worked for me. I forgot to mention that this behavior is also happening with text. Also, thanks for suggesting codesandbox, I forked your code and made it even simpler by removing the images but I still get the same result on my iphone chrome browser. I also tried wrapping everything with an onload function and that didn't work either.

For now I was able to fix this with JQuery animations but I would still like CSS3 transitions to work on my site.

https://codesandbox.io/s/animation-test-forked-tqurn?file=/index.html

enter image description here

Upvotes: 5

Views: 5331

Answers (5)

llccrr
llccrr

Reputation: 114

I am also facing this issue, and so I did a little digging and find some helpful resources to track this bug, which is indeed an iOS Chrome bug. I hope that these resources can help those who come across this bug and pass by here.

The related topic initiated in 2018 on chromium : https://bugs.chromium.org/p/chromium/issues/detail?id=899130

A more recent and active topic :

https://bugs.chromium.org/p/chromium/issues/detail?id=1231712

And finally a post on css-trick which might help you

https://css-tricks.com/forums/topic/problem-with-transition-of-transform-property-in-chrome-on-ios/

Upvotes: 0

Daniel
Daniel

Reputation: 1

A simplified sample running choppy on IOS Google Chrome. Safari runs smooth and painless.

Hope this helps further to narrow down the problem and document the difference.

<div class="menu__icon icon-menu">
  <span></span>
  <span></span>
  <span></span>
</div>
.icon-menu {
    cursor: pointer;
    display: block;
    height: 18px;
    position: absolute;
    left: 28px;
    top: 52px;
    width: 28px;
    z-index: 5;
}

.icon-menu span {
    will-change: transform;
    background-color: #018d8d;
    height: 2px;
    left: 0;
    position: absolute;
    top: calc(50% - 1px);
    -webkit-transition: all .3s ease 0s;
    transition: all .3s ease 0s;
    width: 100%;
}

.icon-menu span:first-child {
    top: -8px;
}

.icon-menu span:nth-child(2) {
    top: 0;
}

.icon-menu span:last-child {
    top: 8px;
}

.icon-menu._active span:first-child {
    top: -1px;
    -webkit-transform: rotate(-45deg);
    transform: rotate(-45deg);
}

.icon-menu._active span {
    -webkit-transform: scale(0);
    transform: scale(0);
}

.icon-menu._active span:last-child {
    top: -1px;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
}
const element = document.querySelector('.menu__icon');

element.addEventListener('click', () => {
  console.log('clicked');
  element.classList.toggle('_active');
});

https://codepen.io/dblue71/pen/dyzMWmO

Upvotes: 0

Do Thanh Binh
Do Thanh Binh

Reputation: 1

I face this situation too. On other browsers, it is smooth. On Chrome mobile, it is so lag. This situation appear when I update my iPhone to iOS 14.

Upvotes: 0

Exodus 4D
Exodus 4D

Reputation: 2812

This looks like a "race condition" issue when loading the page. The JS runs before the IMG request is done.

To understand the problem it is necessary to understand the loading sequence:

  1. On load/reload the Server responds with the document (*.html) file

  2. The browser starts to parse the response (*.html) and starts new requests for each resource found:

    • CSS
    • JS
    • IMGs
  3. These requests complete in an unpredictable order. E.g. large images may load longer than a *.css file,... some resources may already be cached by the browser and won´t start a request at all,...

    If the request for the *.js file completes before the IMGs request is done, there is no rendered height found for that image and the new added CSS class is-visible will start the transition anyway...

  4. Once the IMG requests completes (img gets rendered). A Content Reflow is triggered for the IMG. An ongoing transition on elements that need a repaint (the IMG) is 'reset' and starts from keyframe 0. This may explain your issue.


Here are 3 options that might fix your Issue:

A. Preserve the final dimension of the image.

  • Set a fix height in CSS and add class in html:

    .myImg {
      width: 50px;
      height: 50px;
    }
    
  • You could also add width and height as html attributes. The final dimension is now available in JS even if *.css is still loading...

    <img height="50" width="50" class="..." src="...">
    

B. Add some "load detection" for the images and prevent the transition until image is fully loaded.

  • We check if img is already loaded: src is set and height is detected-
  • Else set an onload event for that image (because it is not loaded yet)
  • Optional: You can use lazy loading for that image and only load images 'on demand' (see final example). The img´s src is set as data-src attribute and and src will be set by JS once the image is available.

Now we can use a isLoaded(element) function to exclude images in .scroll() that are currently not fully loaded.

Here is jsFiddle, or expand the example below...

var elementsToShow = document.querySelectorAll('.show-on-scroll');
$(window).scroll(function() {
    Array.prototype.forEach.call(elementsToShow, function (element) {
        if (isLoaded(element) && isElementInViewport(element)) {
            element.classList.add('is-visible');
        } else {
            element.classList.remove('is-visible');
        }
    });
});

[...elementsToShow].forEach((imgEl, i) => {
    if (
    imgEl.src &&
    imgEl.getBoundingClientRect().height
  ) {
    imgEl.dataset.isLoaded = true;
    console.log(`Img ${i} already loaded`);
  } else {
    console.log(`Img ${i} still loading... or should be lazyloaded`);

    imgEl.onload = function(e) {
      console.log(`Img ${i} finally loaded! onload event`);
        e.target.dataset.isLoaded = true;
        };

    if (imgEl.dataset.src) {
      console.log(`Img ${i} start lazy load...`);
        imgEl.src = imgEl.dataset.src;
    }
  }
})

function isLoaded(el) {
    return el.dataset.isLoaded
}

var elementsToShow = document.querySelectorAll('.show-on-scroll');
$(window).scroll(function() {
  Array.prototype.forEach.call(elementsToShow, function(element) {
    if (isLoaded(element) && isElementInViewport(element)) {
      element.classList.add('is-visible');
    } else {
      element.classList.remove('is-visible');
    }
  });
});

[...elementsToShow].forEach((imgEl, i) => {
  if (
    imgEl.src &&
    imgEl.getBoundingClientRect().height
  ) {
    imgEl.dataset.isLoaded = true;
    console.log(`Img ${i} already loaded`);
  } else {
    console.log(`Img ${i} still loading... or should be lazyloaded`);
    
    imgEl.onload = function(e) {
      console.log(`Img ${i} finally loaded! onload event`);
      e.target.dataset.isLoaded = true;
    };
    
    if (imgEl.dataset.src) {
      console.log(`Img ${i} start lazy load...`);
      imgEl.src = imgEl.dataset.src;
    }
  }
});

function isLoaded(el) {
  return el.dataset.isLoaded
}

// Helper function from: http://stackoverflow.com/a/7557433/274826
function isElementInViewport(el) {
  // special bonus for those using jQuery
  if (typeof jQuery === "function" && el instanceof jQuery) {
    el = el[0];
  }
  var rect = el.getBoundingClientRect();
  return (
    (rect.top <= 0 &&
      rect.bottom >= 0) ||
    (rect.bottom >= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.top <= (window.innerHeight || document.documentElement.clientHeight)) ||
    (rect.top >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight))
  );
}
.balltest {
  width: 50px;
}

.fade-in {
  opacity: 0;
  -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
  will-change: transform, opacity;
}

.standard-push {
  opacity: 0;
  transform: translateY(4em);
  -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out, translateZ(0);
  -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
  will-change: transform, opacity;
}

.is-visible {
  transform: translateY(0);
  opacity: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<p style='margin-top: 100vh;'>above</p>

<img class='balltest show-on-scroll standard-push' src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />
<img class='balltest show-on-scroll fade-in' src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />

<img class='balltest show-on-scroll standard-push' data-src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />
<img class='balltest show-on-scroll fade-in' data-src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />

<p style='margin-bottom: 100vh'>below</p>

</html>

C. Wait for the load event of the document

You can wrap your JS initialization code into a load event for the entire document. The event is fired after all ressources (CSS, IMG,..) were completely loaded.

window.addEventListener('load', (event) => {
    // JS init code hier (images are loaded at this point!)
});

Upvotes: 1

Aaron Sarnat
Aaron Sarnat

Reputation: 1235

I tested your code on Chrome browser for iPhone and could not reproduce the bug displayed in your screen recording.

Could it be that the lag had to do with attempting to run the entire jsfiddle web app on mobile chrome browser? It's a heavy web app with a lot going on under the hood, besides any actual output you're testing, so that might account for performance issues. Better to test just the output by itself.

I've migrated your code to a codesandbox which will allow you to view the output by itself in a mobile browser (see below). You can judge for yourself whether or not the issue you witnessed is an actual code bug.

It should also be noted that the ball image that you're using is quite large in file size (~200kb) for the size it's being displayed at. Therefore, it wouldn't be out of the ordinary to see it flicker while it's loading on the page.

Here's a smaller version of the ball image (downscaled by 80% and optimized with https://tinypng.com/) for a final size of ~42kb (you could definitely make it even smaller):

enter image description here


Here's your same code on codesandbox:

https://codesandbox.io/s/animation-test-ok1dp

Here's just the output (try viewing this in mobile browser on your device):

https://ok1dp.csb.app/

Here's a screen video (that I captured on my iPhone using Chrome browser):

enter image description here

Upvotes: 0

Related Questions