Reputation: 81
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
Upvotes: 5
Views: 5331
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
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
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
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:
On load/reload the Server responds with the document (*.html) file
The browser starts to parse the response (*.html) and starts new requests for each resource found:
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...
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:
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="...">
src
is set and height
is detected-onload
event for that image (because it is not loaded yet)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>
load
event of the documentYou 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
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):
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):
Here's a screen video (that I captured on my iPhone using Chrome browser):
Upvotes: 0