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
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:
CSS
JS
IMGs
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:
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!)
});
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):
https://ok1dp.csb.app/
Here's a screen video (that I captured on my iPhone using Chrome browser):
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.
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
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/
I have a CSS element/ball which I am moving to new coordinates on click.
This works, however the transition I am applying does not seem to take affect.
The ball jumps to the new location. I want it to slowly animate/transition/move to the new coordinates.
What am I doing wrong?
.ball {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #FF5722;
position: absolute;
// d.style.transition = "all 1s ease-in";
transition: all 3s ease-in-out;
// -webkit-transition: all 1s ease-in-out;
// -moz-transition: all 1s ease-in-out;
// -o-transition: all 1s ease-in-out;
// -ms-transition: all 1s ease-in-out;
}
handleClick = (e) => {
console.log('ball clicked');
var d = document.getElementById('ball');
console.log('d', d);
d.style.x = 12 + "px";
d.style.top = 341 + "px";
d.style.transition = "all 1s ease-in";
}
Thanks
You have to assign a default value for x and top or you are trying to transition from nothing.
P.S. It seems your CSS is selecting elements with the CLASS ball instead of an element with the ID of ball. Use #ball instead of .ball in CSS. (credit to jaromanda-x)
window.onclick = (e) => {
console.log('ball clicked');
var d = document.getElementById('ball');
console.log('d', d);
d.style.x = 12 + "px";
d.style.top = 341 + "px";
d.style.transition = "all 1s ease-in";
}
#ball {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #FF5722;
position: absolute;
// d.style.transition = "all 1s ease-in";
transition: all 3s ease-in-out;
// -webkit-transition: all 1s ease-in-out;
// -moz-transition: all 1s ease-in-out;
// -o-transition: all 1s ease-in-out;
// -ms-transition: all 1s ease-in-out;
x:0; /* default value */
top:0; /* default value */
}
<div id="ball">
It seems like there are a few things that need to be corrected;
The ball is styled by the .ball class in your CSS where as the ball element is being accessed via an id, which suggests a potential problem. Is the ball class being applied to the element with id ball?
the x property on the style object should be replaced with the left property to ensure horizontal movement of the ball element
ensure that the transition is assigned to the target elements prior to any CSS properties being modified
Here's an example demonstrating these corrections:
const handleClick = (e) => {
console.log('ball clicked');
const ball = document.getElementById('ball');
/* Setting random coordinates to demonstrate transition */
ball.style.left = Number.parseInt(Math.random() * 200) + "px";
ball.style.top = Number.parseInt(Math.random() * 200) + "px";
}
document.addEventListener("click", handleClick);
#field {
position: relative;
width: 100%;
height: 100%;
}
/* Corrected to id selector with # rather than class selector
with . */
#ball {
position: absolute;
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #FF5722;
position: absolute;
/* Assigning transition behavior which is applied during
property changes */
transition: all 3s ease-in-out;
}
<div id="field">
<div id="ball"></div>
</div>
Hope that helps
I am trying to make a message appear if the user doesn't scroll for specific amount of time and then make the text fade out as soon as the user scroll. What I have tried so far is not working.
I am looking for vanilla javascript solutions only.
thank you for your help.
// make scroll button appear ---------------
var scrollText = document.getElementById("scrollMsg");
function showMsg() {
scrollText.className = "show";
}
setTimeout(showMsg, 2000);
// make scroll button fadout ---------------
function scrollHide() {
var scrollText2 = document.querySelector("#scrollMsg.show");
var scrllTPosition = scrollText2.getBoundingClientRect().top;
var screenPosition = window.innerHeight / 0.5;
if (scrllTPosition < screenPosition) {
scrollText2.classList.add("scrollHide");
}
}
window.addEventListener("scroll", scrollHide);
#scrollMsg {
height: auto;
position: sticky;
bottom: 175px;
z-index: 1;
opacity: 0;
-webkit-transition: opacity 0.7s;
-moz-transition: opacity 0.7s;
transition: opacity 0.7s;
}
#scrollMsg.show {
opacity: 1;
-webkit-transition: opacity 0.7s ease-in-out;
-moz-transition: opacity 0.7s ease-in-out;
transition: opacity 0.7s ease-in-out;
}
#scrollhide {
opacity: 0;
-webkit-transition: opacity 0.7s ease-in-out;
-moz-transition: opacity 0.7s ease-in-out;
transition: opacity 0.7s ease-in-out;
}
<p id="scrollMsg">scroll</p>
I've added some large divs to allow us to scroll through the document.
// make scroll button appear ---------------
var scrollText = document.getElementById("scrollMsg");
window.addEventListener('scroll', (e) => {
console.log('user scrolled!')
scrollText.style.opacity = 0
});
#scrollMsg {
opacity: 1;
transition: opacity 1s;
}
<div style="height:100px"></div>
<p id="scrollMsg">scroll</p>
<div style="height:4000px"></div>
I want to use this functionality in my website (when press in 'SHOW IT') --> http://labs.voronianski.com/jquery.avgrund.js/
The problem is that this functionality need to put body height to 100%. In some time, in me site I need to detect the scroll user to make topbar appear and disappear em some positions.
So to can join this two functionality's, I only change body height to 100% when I press button to show the div avground. But where I have a problem: the button is on page footer and when I change body height, they automatically send me to the top of page and after show de div avground.
Is any way to this don't happen or is possible to scroll to footer before the div avground appear?
If someone have another solution, I appreciate.
Thanks
Have you tried using bootstrap's modals? They are very simple to make and look nicer in my opinion.
http://getbootstrap.com/javascript/
You don't actually have to do that, for that animation you can use simple CSS3:
CSS:
#layer-body {
-webkit-transition: -webkit-transform 0.5s;
-moz-transition: -webkit-transform 0.5s;
transition: transform 0.5s;
}
#layer-body.hide {
-webkit-transform: scale(0.8);
-moz-transform: scale(0.8);
transform: scale(0.8);
}
#menu {
background: rgba(208, 31, 60, .95);
position: fixed; top: 0; left: 0; z-index: 4;
width: 100%; height: 100%;
}
#menu.show {
visibility: visible;
-webkit-transform: translateY(0%);
transform: translateY(0%);
-webkit-transition: -webkit-transform 0.5s;
transition: transform 0.5s;
}
#menu.hide {
visibility: hidden;
-webkit-transform: translateY(100%);
transform: translateY(100%);
-webkit-transition: -webkit-transform 0.5s, visibility 0s 0.5s;
transition: transform 0.5s, visibility 0s 0.5s;
}
HTML:
<body>
<div id="layer-body"></div>
<div id="menu"></div>
</body>
This means, all of your website will be inside #layer-body, and the menu inside #menu.
So when you open the menu you'll have to add the class HIDE to the #layer-body and SHOW to the #menu.
i've got an image that i want to onclick animate the rotation 90degress, when its clicked again i want it to animate the rotation -90degrees.
For the rotation im using the css3 transform:
-moz-transform:rotate(90deg);
-webkit-transform:rotate(90deg);
-ms-transform:rotate(90deg);
For the jquery I want to set a varable to check if the object has been rotated, then act accordingly.
I have been having a real difficult time trying to get it to work. I've put together a JsFiddle.
This is the code I am using:
var turn = true;
$("#button").click(function () {
$("#shape").css('transform', function(index) {
return index * 90;
});
});
Add some transitions and a rotate class, and just toggle that class:
css:
#shape { width: 100px;
height: 200px;
background:#000;
-moz-transition: all 1s ease;
-webkit-transition: all 1s ease;
-o-transition: all 1s ease;
transition: all 1s ease;
}
.rotate {-moz-transform: rotate(90deg);
-webkit-transform:rotate(90deg);
-ms-transform:rotate(90deg);
-o-transform:rotate(90deg);
}
js:
$("#button").click(function() {
$("#shape").toggleClass('rotate');
});
FIDDLE
If I understood correct, THIS should do it.
I think in general, if you're going to use transition's you should target the specific properties you want to affect. I would consider the use of "all" to be poor practice.
Target alternative:
css:
#shape {
width: 100px;
height: 200px;
background:#000;
-moz-transition: transform 1s ease;
-webkit-transition: transform 1s ease;
-o-transition: transform 1s ease;
transition: transform 1s ease;
}
.rotate {
-moz-transform: rotate(90deg);
-webkit-transform:rotate(90deg);
-ms-transform:rotate(90deg);
-o-transform:rotate(90deg);
transform:rotate(90deg);
}
///jquery
$("#button").click(function() {
$("#shape").toggleClass('rotate');
});