CSS animation bug in Safari - javascript

I have a CSS animation with a delay and I pause it during the delay.
It works as expected on Firefox and Chrome, the "Hello" does not move.
However on Safari, the animation jumps to the last frame.
Why and how to fix please?
function test() {
var timeout = 1000;
setTimeout(function() {
document.getElementById('animation').style.animationPlayState = 'paused';
}, timeout);
}
document.addEventListener("DOMContentLoaded", test);
#animation {
animation: test 2s linear 2s;
}
#keyframes test {
to {
transform: translateY(100px);
}
}
<div id="animation">
Hello (this text should not move)
</div>
If I remove the 2s delay, set the duration to 4s, and add a keyframe with transform:none, I can make this simple example work. However my real case has multiple animations that are synchronized with delays.

The Safari behaviour is only buggy when timeout is set to a value smaller than the animation delay. So, a workaround is to set the initial state to paused via animation-play-state and then control it via JS, as shown below:
function test() {
let el = document.getElementById("animation");
let timeout = 1000;
// Get the delay. No luck with el.style.animationDelay
let delay =
window
.getComputedStyle(el)
.getPropertyValue("animation-delay")
.slice(0, -1) * 1000;
// Only resume and later pause when timeout is greater than animation delay
if (timeout > delay) {
el.style.animationPlayState = "running";
setTimeout(function() {
el.style.animationPlayState = "paused";
}, timeout);
}
}
document.addEventListener("DOMContentLoaded", test);
#animation {
animation: test 2s linear 3s;
animation-play-state: paused; /* Pause it right after you set it */
}
#keyframes test {
to {
transform: translateY(100px);
}
}
<div id="animation">
Hello (this text should not move)
</div>
Try different timeout values to see it working. Can't say why this is happening though. Looks like a bug to me. Tested on OS X El Capitan 10.11.6 / Safari 11.0 (11604.1.38.1.7).
Codepen demo

This is not an answer to the problem. However, if you remove the animation delay, pausing and restarting the animation works as it should. It seems then the animation delay is what is causing the problem. Perhaps rather than relying on css to handle the delay, programmatically control animation delay with javascript.
See below pausing and running the animation
function test() {
var timeout = 1000;
setTimeout(function() {
document.getElementById('animation').style.animationPlayState ='paused';
document.getElementById('animation').style.webkitAnimationPlayState ='paused';
}, timeout);
setTimeout(function() {
document.getElementById('animation').style.animationPlayState='running';
document.getElementById('animation').style.webkitAnimationPlayState ='running';
}, timeout * 2);
}
document.addEventListener("DOMContentLoaded", test);
#animation {
-webkit-animation: test 2s linear;
animation: test 2s linear;
}
#-webkit-keyframes test {
to {
-webkit-transform: translateY(100px);
transform: translateY(100px);
}
}
#keyframes test {
to {
-webkit-transform: translateY(100px);
transform: translateY(100px);
}
}
<div id="animation">
Hello (this text should not move)
</div>

Related

Opacity transition css animation works from higher to lower but not vice versa

I have what should be a very simple problem, perhaps a typo that I just can't see.I have a page blocker that grays the page out with an opacity transition and blocks any clicks when I launch a form. This works in hideForm, but does not in showForm and it immediately become 0.4 opacity. Weird because all they are doing is the opposite of each other with a timeout in hideForm to set's it to display to none when opacity transition is finished.
I think this will end being a simple solution and I'll end up being asked to delete the question, which I will gladly do, but I've been trying to solve this for too long and I need a second pair of eyes.
I tried to minimize the amount of code shown but will post more if asked.
#pageCover {
opacity: 0;
-moz-transition: opacity 1s;
-webkit-transition: opacity 1s;
transition: opacity 1s;
display:none
}
This works great
function hideForm() {
if (form.style.top > '0px') {
pageCover.style.opacity = 0.0;
setTimeout(function () { pageCover.style.display = 'none'; }, 1000);
}
}
This displays the pageCover but ignores the transition and goes right to 0.4 . How can the transition work one way, but not the opposite way? I'm stumped.
function showForm() {
if (form.style.top < '0px') {
pageCover.style.opacity = 0.4;
pageCover.style.display = 'block';
}
}
The opacity attribute animates but not the "display" attribute. When they are set at the same time, the opacity transition will not be observed. You can play with the numbers but for illustration's sake, let's add a 10ms gap between the two operations:
pageCover.style.display = 'block';
setTimeout(function(){
pageCover.style.opacity = 0.4;
},10);
You can push the number to even 0, but the browser might "optimize" it away.
Live Example:
const pageCover = document.getElementById("pageCover");
function hideForm() {
pageCover.style.opacity = 0;
setTimeout(function () {
pageCover.style.display = 'none';
}, 1000);
}
function showForm() {
pageCover.style.display = 'block';
setTimeout(function () {
pageCover.style.opacity = 0.4;
}, 10);
}
document.getElementById("btn-show").addEventListener("click", showForm);
document.getElementById("btn-hide").addEventListener("click", hideForm);
#pageCover {
opacity: 0;
-moz-transition: opacity 1s;
-webkit-transition: opacity 1s;
transition: opacity 1s;
display: none;
}
<div id="pageCover">
This is the page cover
</div>
<input type="button" id="btn-show" value="Show">
<input type="button" id="btn-hide" value="Hide">

JS changing source image with animation

I have a problem with looped fade-in/fade-out image source changing in JS and CSS and using SetTimeout() callback.
The problem is, that the sequence is working strange: sometimes the image changes before the transition starts, sometimes it works fine, and sometimes in the other way.
Here is my JS:
const animationTime = 5000;
const transitionTime = 500;
function nextImage() {
let img = document.getElementById('img1');
img.classList.remove('hidden');
setTimeout(function () {
img.classList.add('hidden');
},animationTime-transitionTime);
img.src=randomize();
setTimeout(nextImage, animationTime);
}
randomize() function just gets a random image path from array.
Here is HTML:
<div class="some-class">
<img class="some-image" id="img1" src="1.png">
</div>
And here is CSS:
.some-image {
transition: opacity 0.5s linear;
-webkit-transition: opacity 0.5s linear;
-o-transition: opacity 0.5s linear;
-moz-transition: opacity 0.5s linear;
-moz-border-radius: 15px;
}
.hidden {
opacity: 0;
}
Upd.
So I have edited CSS file:
.some-image {
width: 370px;
height: 190px;
animation: fade-out;
animation-duration: 1s;
}
.hidden {
animation: fade-out;
animation-duration: 1s;
}
#keyframes fade-in {
from {opacity: 0;}
to {opacity: 1;}
}
#keyframes fade-out {
from {opacity: 1}
to {opacity: 0}
}
And JS-file:
function nextImage() {
let img = document.getElementById('img1');
img.classList.remove('hidden');
setTimeout(function () {
img.classList.add('hidden');
},animationTime-1000);
img.src=randomize();
}
setTimeout(nextImage, animationTime);
}
And, somehow, it works perfectly on a local machine, but on a dedicated website animation sometimes fades-in before the image source changed.
I think the problem is about timing. The setTimeout function didn't guarantee to execute exactly time as argument set. So there is a possibility that you change the src of image before/after it add/remove hidden class. These delay is rarely happens that might be the reason why it works on your machine.
So this problem can solve by every time you change the image you must have to make sure the image is completely hide.
const nextImage = function () {
let img = document.querySelector('img')
img.classList.add('hidden')
setTimeout(() => {
img.style.visibility = 'hidden'
img.src = randomImage()
// skip to next frame, may be this not necessary to use setTimeout
setTimeout(() => {
img.style.visibility = ''
img.classList.remove('hidden')
}, 10)
}, animationDuration)
setTimeout(nextImage, intervalDuration + animationDuration)
}
The new cycle will be: fade image out, wait for animation then change image (with set visibility to hidden) and then fade in. And loop.
With this approach. If setTimeout is early execute before the image has completely fade out the visibility will be set hidden. If it's delayed, the image will be hide a bit longer.
Live example here. In that code I add a little bit noise with random time to test.
Unfortunately, After I spent an hour to see my answer is right I still feel it's not perfect anyway and it will be worse if you image is large. I would recommend you try two or more img tags instead.
You should try using css animations instead. You can easily implement the above with it, and it will save you the trouble of handling animations in your code.

Can I detect with JavaScript where we are percentage wise in a CSS Keyframe animation

I was thinking. I know I can detect when a CSS animation has started, finished or is repeated by listening for the animationstart, animationiteration, animationend events (obviously we are missing browser prefixes there), for example:
document.getElementById('identifier')
.addEventListener("animationstart", function(){
// do something...
});
but I was wondering, is it possible to determine where we are are running a CSS animation, how for example with the following could I listen for when we are at 50% of the keyframe animation:
<!DOCTYPE html>
<html>
<head>
<style>
#animateDiv {
width: 100px;
height: 100px;
background-color: red;
position: relative;
animation-name: example;
animation-duration: 4s;
}
#keyframes example {
0% {background-color:red; left:0px; top:0px;}
25% {background-color:yellow; left:200px; top:0px;}
50% {background-color:blue; left:200px; top:200px;}
75% {background-color:green; left:0px; top:200px;}
100% {background-color:red; left:0px; top:0px;}
}
</style>
</head>
<body>
<div id="animateDiv"></div>
<script>
// what do I do here? How do I listen for the 50% event of the keyframes?
document.getElementById('animateDiv').addEventListener('animation at 50%?', function() {
console.log('got it');
})
</script>
</body>
</html>
Ello, mate. I dont know if you can get the exact keyframe from a CSS animation, but u can use some mathemathics to get it, like our mate Paulie_D suggested.
On your code, your animation is 4s length, so, the keyframe 50% of the annimation is after 2s:
//......
//when the animation starts....
setTimeout(function(){
//Enter your stuff here
}, 2000); //2 seconds delay, considering your animation length = 4s;
You can also use (needs jQuery):
$("#animated_element").bind("half_animation", function(){
//Ya stuff here
});
//.........
//When the animation starts...
setTimeout(function()
{
$("#animated_element").trigger("half_animation");
}, 2000);
Or:
$("#animated_element").bind("half_animation", function(){
once = 0;
setTimeout(function()
{
//Ya stuff here....
}, 2000)
});
//........
//When the animation starts
$("#animated_element").trigger("half_animation");
I hope it help ya, mate.
G'day mate. Here's my take on it.
Get the animation duration.
Sprinkle some mathematics on it like Vini suggested.
setTimeout()
Code:
const fn_runAtKeyframe = (DOMElement, keyframe, callback) => {
const animationDuration = window.getComputedStyle(DOMElement).animationDuration;
// animationDuration returns string, e.g. "5s" or "500ms", so need to parseInt()
// if it is in seconds, change it to milliseconds
let animationKeyframe
if (animationDuration.replace(/[0-9]/g, '') === "s") {
animationKeyframe = parseInt(animationDuration) * keyframe * 1000
} else {
animationKeyframe = parseInt(animationDuration) * keyframe
}
const doStuff = (e) => {
setTimeout(() => {
console.log(`Function "${callback.name}" will run at ${keyframe*100}% keyframe`)
callback();
}, animationKeyframe)
}
DOMElement.addEventListener("animationstart", doStuff);
// or use "animationiteration" depending on your usecase
}
Run it,
const animateDiv = document.querySelector("#animateDiv")
const keyframe = 0.5 // at 50% keyframe
const callback = () => {
// do stuff here...
};
fn_runAtKeyframe(animateDiv, keyframe, callback)
Like Vini said u have that chance, I make this for myself, you can change the "animation-play-state:" for any function u want. Salute
//Animation pause at specific percentage, Example: pauseAnimationAt("myDiv",33,10)
function pauseAnimationAt(l,p,d){
percentage = p/d*1000;
function pauseAnimation(){
setTimeout(function(){ document.getElementById(l).style="animation-play-state: paused"; }, percentage);//l = Element ID, p = percentage when you wanna stopt animation, d = Duration of animation (Yes, you need to know that)
}
document.getElementById(l).addEventListener("animationstart", pauseAnimation());
}

Move a div back and forth across page

I'm trying to get a div to move from one end of the screen to the other on a loop.
My javascript currently only attempts to move the div left but doesn't work.
var func = function() {
$("#bob").animate({"left": "-40px"}, 1000, function() {
$(this).animate({"left": "40px"}, 1000)
})
setTimeout(func, 2000);
}
Here is my jsfiddle:
http://jsfiddle.net/zsal/N48Eg/1/
Name your functions, and then use one as the completion callback in the other:
function goLeft() {
$('#bob').animate({'left': '-40px'}, 1000, goRight);
}
function goRight() {
$('#bob').animate({'left': '40px'}, 1000, goLeft);
}
goLeft();
So, when it's done going left, it should go right. When it's done going right, it should go left.
Disclaimer: Untested
P.S. You're missing jQuery in your Fiddle.
Your current fiddle doesn't work because you didn't include jQuery (from the Frameworks & Extensions drop-down on the left) and because you define the func() function but never actually call it. Fix those two things and it will work as shown here: http://jsfiddle.net/N48Eg/8/
Note, however, that your animation code is more complicated than it needs to be. Multiple animations on the same element will be queued automatically by jQuery, so you don't need to use a callback on the first one to start the second. And you can supply func as the callback on the second and avoid the setTimeout() completely:
var func = function() {
$("#bob").animate({"left": "-40px"}, 1000)
.animate({"left": "40px"}, 1000, func);
}
func();
Demo: http://jsfiddle.net/N48Eg/18/
You just haven't called your function...
var func = function() {
$("#bob").animate({"left": "-40px"}, 1000, function() {
$(this).animate({"left": "40px"}, 1000)
})
setTimeout(func, 2000); // added back
}
func();
Your updated fiddle
First of, if you want the motion to continue, you need to use setInterval instead of setTimeout
Also you need to either move the setInterval out of the scope of func, or call func to start the animation
lots of problems. Heres a fiddle with a solution.
you also forgot to position:relative your absolutely positioned elements container.
JSFIDDLE
you weren't calling your function for starters
i made a new one called moveBob()
Here's the pure CSS version. http://jsfiddle.net/zDSRd/
#bob
{
position:absolute;
animation: fly 2s linear 0s alternate infinite;
-webkit-animation: fly 2s linear 0s alternate infinite;
-moz-animation: fly 2s linear 0s alternate infinite;
-o-animation: fly 2s linear 0s alternate infinite;
-ms-animation: fly 2s linear 0s alternate infinite;
}
#keyframes fly {
from { transform: translateX(0px); }
to { transform: translateX(500px); }
}
#-webkit-keyframes fly {
from { -webkit-transform: translateX(0px); }
to { -webkit-transform: translateX(500px); }
}
#-moz-keyframes fly {
from { -moz-transform: translateX(0px); }
to { -moz-transform: translateX(500px); }
}
#-o-keyframes fly {
from { -o-transform: translateX(0px); }
to { -o-transform: translateX(500px); }
}
#-ms-keyframes fly {
from { -ms-transform: translateX(0px); }
to { -ms-transform: translateX(500px); }
}

How can I start CSS3 Animations at a specific spot?

I'm using CSS3 Animations, and I want to be able to move to a specific spot in the animation. For instance, if the CSS looks like this (and pretend that I used all the proper prefixes):
#keyframes fade_in_out_anim {
0% { opacity: 0; }
25% { opacity: 1; }
75% { opacity: 1; }
100% { opacity: 0; }
}
#fade_in_out {
animation: fade_in_out_anim 5s;
}
then I would like to be able to stop the animation, and move it to the 50% mark. I guess that the ideal JavaScript would look something like this:
var style = document.getElementById('fade_in_out').style;
style.animationPlayState = 'paused';
// Here comes the made up part...
style.animation.moveTo('50%'); // Or alternately...
style.animationPlayPosition = '50%';
Does anyone know of a way to make this happen (hopefully in Webkit)?
We can use the animation-delay property. Usually it delays animation for some time, and, if you set animation-delay: 2s;, animation will start two seconds after you applied the animation to the element. But, you also can use it to force it to start playing animation with a specific time-shift by using a negative value:
.element-animation{
animation: animationFrames ease 4s;
animation-delay: -2s;
}
http://default-value.com/blog/2012/10/start-css3-animation-from-specified-time-frame/

Categories

Resources