After changing the animation-duration (or in this case, -webkit-animation-duration) property via JavaScript with setProperty("-webkit-animation-duration", value + "s"), I see the change in the element inspector in Chrome, but the actual animation speed doesn't change. In addition, if I manually change the value in the element inspector, there is no change either.
I've got an input field set up to take an animation speed value, which is connected to the following event listener (orbitFactor is a global var defined elsewhere):
function updateSpeed(event) {
var planetDiv = document.getElementById(event.target.id);
planetDiv.style.setProperty("width", event.target.value / orbitFactor);
planetDiv.style.setProperty("height", event.target.value / orbitFactor);
planetDiv.style.setProperty("-webkit-animation-duration", event.target.value + "s");
}
The event listener is definitely getting called, and the -webkit-animation-duration value does change in the element inspector, but the speed of the animation doesn't. Is there something I'm missing here with regards to -webkit-animation-duration? The other properties I'm changing (e.g. width and height) using the same method do change visibly.
Thanks in advance
EDIT: Note that this is a problem in Chrome 40, but it works properly in Chrome 42 and Firefox 35.
Setting the style element directly using the [] to access either the vendor-prefixed or native css prop. will allow you to re-apply the animation duration property and change the rotational speed of the planet. No jquery needed. It's also worth mentioning that at the time of writing Firefox supports a non-prefixed version of the css property, while there is either mixed support or vendor-prefix support for other browsers. If considering using these animations, a given developer should seriously consider their potential user-base and probably not make this a core feature of web app. See more support info here:
http://caniuse.com/#feat=css-animation
Le code:
orbitFactor = 1e6
function updateSpeed(event) {
var orbitDiv = document.getElementById("Mercury-orbit");
orbitDiv.style["-webkit-animation-duration"] = event.target.value + "s";
}
function updateDiameter(event) {
var planetDiv = document.getElementById("Mercury");
planetDiv.style["width"] = event.target.value + "px";
planetDiv.style["height"] = event.target.value + "px";
}
document.getElementById("orbit-period").addEventListener("change", updateSpeed);
document.getElementById("planet-diameter").addEventListener("change", updateDiameter);
It's not easy to restart CSS animation or change its parameter. However, I found some trick. See the following code. I separated the animation parameters into the class, which I add / remove. Plus the trick found in CSS-Tricks article and it works:
$(document).ready(function() {
$('#slow-btn').click(function(){
$('#testdiv').removeClass("testanimation");
$('#testdiv').css("-webkit-animation-duration", "5s");
$('#testdiv').get(0).offsetWidth = $('#testdiv').get(0).offsetWidth;
$('#testdiv').addClass("testanimation");
});
$('#fast-btn').click(function(){
$('#testdiv').removeClass("testanimation");
$('#testdiv').css("-webkit-animation-duration", "1s");
$('#testdiv').get(0).offsetWidth = $('#testdiv').get(0).offsetWidth;
$('#testdiv').addClass("testanimation");
});
});
#testdiv {
display: block;
position: absolute;
left: 100px;
width: 100px;
height: 100px;
background: red;
}
.testanimation {
-webkit-animation: myanimation 2s linear alternate infinite;
animation: myanimation 2s linear alternate infinite;
}
#-webkit-keyframes myanimation {
from {left: 100px;}
to {left: 400px;}
}
#keyframes myanimation {
from {left: 100px;}
to {left: 400px;}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='testdiv' class='testanimation'></div>
<input id='slow-btn' type='button' value='slow' />
<input id='fast-btn' type='button' value='fast' />
in w3c standard, it says that it doesn't mention we can change the animation duration time or not. so it all depends on explorer.chrome yes, but ie no. So, we should update the whole animation when we want to change the time.
var animation = 'animationName time linear infinite';
var $element= $('selector').css('animation', 'none');
setTimeout(function(){
$element.css('animation', animation);
});
this work on IE
Related
I have a large game project that used extensive jquery in its code. Some time ago I stripped out all of the jquery and replaced it with pure JS, but the one thing I had trouble with was replacing the .animation calls for projectiles in the game.
It appeared that I should replace them with CSS transitions, and the game needed to know when the transition was done, so I needed to add a callback to the transition. All well and good, except when I assigned new location values for the projectile, the transition was skipped entirely and no callback was called. For some ungodly reason, it started working if I wrapped the change to its css position in a SetTimeout for 1ms--and even then, sometimes it would still skip the transition.
Now it usually works, except about one time in 10 the transition will play but then the callback will not be called. I don't know why the settimeout helps to be there, and I don't know why the callback sometimes doesn't work. Can anyone help me understand?
let tablehtml = '<div id="'+animid+'" style="position: absolute; left: ' + ammocoords.fromx + 'px; top: ' + ammocoords.fromy + 'px; background-image:url(\'graphics/' + ammographic.graphic + '\');background-repeat:no-repeat; background-position: ' + ammographic.xoffset + 'px ' + ammographic.yoffset + 'px; transition: left '+duration+'ms linear 0s, top '+duration+'ms linear 0s;"><img src="graphics/spacer.gif" width="32" height="32" /></div>';
document.getElementById('combateffects').innerHTML += tablehtml;
let animdiv = document.getElementById(animid);
animdiv.addEventListener("transitionend", function(event) {
FinishFirstAnimation();
}, false);
setTimeout(function() { Object.assign(animdiv.style, {left: ammocoords.tox+"px", top: ammocoords.toy+"px" }); }, 1); // THIS IS A TOTAL KLUDGE
// For some reason, the transition would not run if the 1ms pause was not there. It would skip to the end, and not
// fire the transitionend event. This should not be necessary.
For a comprehensive explanation of why this happens, see this Q/A, and this one.
Basically, at the time you set the new style the browser still has not applied the one set inline, your element's computed style still has its display value set to ""
, because it's what elements that are not in the DOM default to.
Its left and top computed values are still 0px, even though you did set it in the markup.
This means that when the transition property will get applied before next frame paint, left and top will already be the ones you did set, and thus the transition will have nothing to do: it will not fire.
To circumvent it, you can force the browser to perform this recalc. Indeed a few DOM methods need the styles to be up to date, and thus browsers will be forced to trigger what is also called a reflow.
Element.offsetHeight getter is one of these method:
let tablehtml = `
<div id="spanky"
style="position: absolute;
left: 10px;
top: 10px;
background-color:blue;
width:20px;
height:20px;
transition: left 1000ms linear 0s, top 1000ms linear 0s;">
</div>`;
document.body.innerHTML += tablehtml;
let animdiv = document.getElementById('spanky');
animdiv.addEventListener("transitionend", function(event) {
animdiv.style.backgroundColor='red';
}, false);
// force a reflow
animdiv.offsetTop;
// now animdiv will have all the inline styles set
// it will even have a proper display
animdiv.style.backgroundColor='green';
Object.assign(animdiv.style, {
left: "100px",
top: "100px"
});
But really, the best is probably to directly use the Web Animation API, which is supported almost everywhere (but in long dead IE). This will take care of doing the right thing at the right time.
let tablehtml = `
<div id="spanky"
style="
position: absolute;
background-color:blue;
width:20px;
height:20px;
"
</div>`;
document.body.innerHTML += tablehtml;
let animdiv = document.getElementById("spanky");
const anim = animdiv.animate([
{ left: "10px", top: "10px" },
{ left: "100px", top: "100px" }
],
{
duration: 1000,
fill: "forwards"
});
anim.finished.then(() => {
animdiv.style.backgroundColor = "green";
});
it has to do with the timing of when the new element is actually "painted" ...
I know this is a kludge too but ...
the one way I've found to guarantee 100% success is to wait just under two frames (at 60fps that's about 33.333ms - but the setTimeout in browsers these days has a artificial "fudge" added due to spectre or something - anyway ...
requestAmiationFrame(() => requestAnimationFrame() => { ... your code ...})) does the same thing except it could be as little as 16.7ms delay, i.e. just over one frame
let tablehtml = `
<div id="spanky"
style="position: absolute;
left: 10px;
top: 10px;
background-color:blue;
width:20px;
height:20px;
transition: left 1000ms linear 0s, top 1000ms linear 0s;">
</div>`;
document.body.innerHTML += tablehtml;
let animdiv = document.getElementById('spanky');
animdiv.addEventListener("transitionend", function(event) {
animdiv.style.backgroundColor='red';
}, false);
requestAnimationFrame(() => requestAnimationFrame(() => {
animdiv.style.backgroundColor='green';
Object.assign(animdiv.style, {
left: "100px",
top: "100px"
});
}));
having a single requestAnimationFrame failed about 1 in 10 for me, but the double request makes it impossible to fail
I was able to get rid of setTimeout() by following the Web Animation Approach suggested by Kaiido.
But for me anim.finished.then(()=>{}); is not invoked after the frame is actually painted. It took 2ms to invoke this callback.
For my case anim.onfinish=()=>{} makes sure the frame is painted and it took 16ms to invoke the callback.
I have the following CSS snippet:
.Test-Content {
transition: height 2s;
}
and the following HTML code:
<div id="mydiv" class="Test-Content">foo</div>
Using pure JavaScript, I am trying to change the height of the div like that:
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 0;";
/* ... Lots of code and situations where the browser re-renders the page ... */
myId.style.cssText = "height: 100px";
This indeed changes the div's height from whatever it was to 0 and then to 100px, but without any animation, i.e. immediately. I don't understand why this happens. After all, the div has .Test-Content in its class list, so any change of its height should trigger the transition.
Could anybody please explain why this is not the case?
When I change this to the following (very weird and worrying) code, it works as expected:
CSS:
.Test-Content-A {
transition: height 2s;
height: 0;
}
.Test-Content-B {
transition: height 2s;
height: 10px; /* or whatever number you prefer */
}
HTML:
<div id="mydiv" class="Test-Content-A Test-Content-B">foo</div>
JavaScript: (Same as above)
It seems that I can trigger a transition by setting an element's style.cssText directly only if this element also has two classes with different height properties in its class list.
I have that problem in Firefox and Chrome (didn't test others so far), both at current patch level at the time of writing this.
There are a couple things you can be running into as illustrated by the following snippet. The first time you click "Expand" or "Collapse", it won't animate, but then clicking "Expand" or "Collapse" after that will trigger an animation.
document.getElementById("expand").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 100px";
});
document.getElementById("collapse").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 0;";
});
document.getElementById("unset").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "";
});
document.getElementById("setandexpand").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 0;";
setTimeout(() => { myId.style.cssText = "height: 100px"; });
});
.Test-Content {
transition: height 2s;
}
<div id="mydiv" class="Test-Content">foo</div>
<button id="expand">Expand</button>
<button id="collapse">Collapse</button>
<button id="unset">Unset</button>
<button id="setandexpand">Set and expand</button>
Issues you may be experiencing:
1. CSS can only transition from a value to a value.
Going from cssText = "" (the initial value) to cssText = "height: 100px" doesn't animate. You can reproduce this by clicking "Unset" then clicking "Expand" or "Collapse".
2. If CSS is set multiple times in a block of code, the browser will only process the last one.
The browser doesn't render changes immediately when they're set but instead switches between executing all executable JavaScript and updating the page based on what everything is set to. You can get around this by breaking it into two discrete steps. The best will probably be setting style="height: 0" in the HTML or adding a class with zero height.
Otherwise, you can do something like:
myId.style.cssText = "height: 0";
setTimeout(() => { myId.style.cssText = "height: 100px"; });
This code sets the height to zero, lets the browser update the style, then executes the new code setting the height to 100px, which the browser can animate. Of course, you only need to call cssText = "height: 0" because as soon as it's been rendered the browser will be able to animate.
You can see this in the snippet by clicking "Unset" followed by "Set and expand". The element will immediately decrease to zero and then expand to 100px. Clicking multiple times shouldn't appear to do anything because each time the browser will start animating down to zero then start animating back up to 100px within milliseconds.
One of the easiest ways to trigger CSS animations with JS is changing the class.
CSS:
.Test-Content {
height: 0;
transition: height 2s;
}
.Test-Content.taller {
height: 10px;
}
And then, with Javascript:
1) For adding the class (animating forwards)
document.getElementById("mydiv").classList.add('taller');
2) For removing the class (animating backwards)
document.getElementById("mydiv").classList.remove('taller');
This may not answer your question about why the transition isn't working with style.cssText. However, if you're wanting the div to change height right when the page loads, I recommend using an animation instead like the following:
.Test-Content {
transition: height 2s;
height: 100px;
animation: grow 2s;
}
#keyframes grow {
from {
height: 0
}
to {
height: 100px;
}
}
This eliminates the need to use JavaScript and better helps to keep JavaScript from doing what CSS can do.
This question already has answers here:
How can I force WebKit to redraw/repaint to propagate style changes?
(33 answers)
Closed 4 years ago.
Currently I am working on an animation for a website which involves two elements having their position changed over a period of time and usually reset to their initial position. Only one element will be visible at a time and everything ought to run as smoothly as possible.
Before you ask, a CSS-only solution is not possible as it is dynamically generated and must be synchronised. For the sake of this question, I will be using a very simplified version which simply consists of a box moving to the right. I shall be referring only to this latter example unless explicitly stated for the remainder of this question to keep things simple.
Anyway, the movement is handled by the CSS transition property being set so that the browser can do the heavy lifting for that. This transition must then be done away with in order to reset the element's position in an instant. The obvious way of doing so would be to do just that then reapply transition when it needs to get moving again, which is also right away. However, this isn't working. Not quite. I'll explain.
Take a look at the JavaScript at the end of this question or in the linked JSFiddle and you can see that is what I'm doing, but setTimeout is adding a delay of 25ms in between. The reason for this is (and it's probably best you try this yourself) if there is either no delay (which is what I want) or a very short delay, the element will either intermittently or continually stay in place, which isn't the desired effect. The higher the delay, the more likely it is to work, although in my actual animation this causes a minor jitter because the animation works in two parts and is not designed to have a delay.
This does seem like the sort of thing that could be a browser bug but I've tested this on Chrome, Firefox 52 and the current version of Firefox, all with similar results. I'm not sure where to go from here as I have been unable to find this issue reported anywhere or any solutions/workarounds. It would be much appreciated if someone could find a way to get this reliably working as intended. :)
Here is the JSFiddle page with an example of what I mean.
The markup and code is also pasted here:
var box = document.getElementById("box");
//Reduce this value or set it to 0 (I
//want rid of the timeout altogether)
//and it will only function correctly
//intermittently.
var delay = 25;
setInterval(function() {
box.style.transition = "none";
box.style.left = "1em";
setTimeout(function() {
box.style.transition = "1s linear";
box.style.left = "11em";
}, delay);
}, 1000);
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
Force the DOM to recalculate itself before setting a new transition after reset. This can be achieved for example by reading the offset of the box, something like this:
var box = document.getElementById("box");
setInterval(function(){
box.style.transition = "none";
box.style.left = "1em";
let x = box.offsetLeft; // Reading a positioning value forces DOM to recalculate all the positions after changes
box.style.transition = "1s linear";
box.style.left = "11em";
}, 1000);
body {
background-color: rgba(0,0,0,0);
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
See also a working demo at jsFiddle.
Normally the DOM is not updated when you set its properties until the script will be finished. Then the DOM is recalculated and rendered. However, if you read a DOM property after changing it, it forces a recalculation immediately.
What happens without the timeout (and property reading) is, that the style.left value is first changed to 1em, and then immediately to 11em. Transition takes place after the script will be fihished, and sees the last set value (11em). But if you read a position value between the changes, transition has a fresh value to go with.
Instead of making the transition behave as an animation, use animation, it will do a much better job, most importantly performance-wise and one don't need a timer to watch it.
With the animation events one can synchronize the animation any way suited, including fire of a timer to restart or alter it.
Either with some parts being setup with CSS
var box = document.getElementById("box");
box.style.left = "11em"; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = "1em";
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
animation: move_me 1s linear 4;
}
#keyframes move_me {
0% { left: 1em; }
}
<div id="box"></div>
Or completely script based
var prop = 'left', value1 = '1em', value2 = '11em';
var s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = '#keyframes move_me {0% { ' + prop + ':' + value1 +' }}';
document.getElementsByTagName('head')[0].appendChild(s);
var box = document.getElementById("box");
box.style.animation = 'move_me 1s linear 4';
box.style.left = value2; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = value1;
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
I have a message which I display on the screen when a user clicks on some links. I use a transition on the opacity to get the message to fade away.
The problem is that when a user clicks on the next link which is supposed to display the message, the element has its opacity set to 0 (thus it's not visible).
The opacity transition is triggered by a JavaScript function.
My question: would it be possible to reset the opacity (back to 1) before the transition effect happens?
I only see a nasty way such as triggering a function from within the function that triggers the opacity transition, to reset the opacity back to 1. Something like:
setTimeout(function(){elem.style.opacity = 1;)}, 3000);
But this is not great because I'd like the opacity to be reset as soon as a user clicks another link for which this message is displayed.
ideas?
EDIT:
Fiddle
HTML:
<div id="pop_up" class="pop_up">negative</div>
<a class="something_else" href="#" onclick="show(event, this); return false;">toto</a>
<a class="something_else" href="#" onclick="show(event, this); return false;">titi</a>
CSS:
.pop_up
{
position: absolute:
top: -10px;
padding: 10px;
background-color: orange;
opacity: 1;
transition: opacity 1s linear;
}
JS:
function show(e, elem)
{
msg = document.getElementById("pop_up");
msg.style.top = elem.offsetTop;
msg.style.left = elem.offsetLeft;
msg.style.opacity = 0;
msg.innerHTML = "Hug Me!";
}
I think I know what you want. You need to reset the transition to none and back again each time, and also reset the opacity each time and hide/show. Using the following CSS:
.pop_up
{
position: absolute;
display: none;
padding: 10px;
background-color: orange;
}
and the following javascript:
function show(e, elem) {
var msg = document.getElementById("pop_up");
msg.style.transition = 'none';
msg.style.display = 'none';
msg.style.opacity = 100;
msg.innerHTML = "Hug Me!";
msg.style.display = 'block';
msg.style.top = '' + elem.offsetTop + 'px';
msg.style.left = '' + elem.offsetLeft + 'px';
msg.style.transition = 'opacity 2s linear';
msg.style.opacity = 0;
}
I think you get the effect you want. I have changed to a 2s transition to see ity better, that you can adjust. The point is to hide and reset the popup each time, then reset the transition and show so that the transition runs again.
I have made msg a local vairable (with the vardeclaration) but also agree with the comments that using global functions and inline event handlers like this is not ideal. Improvments to that depnd on if you want to use a js library and if so which or if you want to stick to pure js.
Working fiddle (only tested in Firefox).
This technique stopped working with me, until I nailed done what actually make it work. It's necessary to keep this line in the JS code:
msg.style.bottom = elem.offsetTop + 'px';
If you remove it, it seems like the CSS for the element is not re-evaluated, which means the transition is actually not reset from 'none' to 'opacity 1s ease' for instance, which actually triggers the transition. So, if you remove the lines that actually reset the position of the div, the css won't be re-evaluated. In my case, I ended up needing the element to have a fixed position. So I first do:
msg.style.bottom = elem.offsetTop + 'px';
Immediately followed by:
msg.style.bottom = 0;
The first call internally forces the transition to be reset from none to something else, and then of course I finally positioned the element where I want.
Note that the first line might as well be:
var tmp = elem.offsetTop;
What's important here, is to change the state of the element, so that its CSS is being internally re-evaluated by the browser engine.
I'm trying to create a fading out and fading in effect using JavaScript and CSS3. The goal is to have a div shrink in width when clicked and have the text contained within it simultaneously fade out. Then when it is clicked again, the div expands back to its normal width, and the text fades back in.
Here is the HTML:
<div id="box1" onclick="slide1()">
<p class="fader">Lorem ipsum.</p>
</div>
Here is the CSS:
#box1 {
position:relative;
left:0%;
top:0%;
width: 70%;
height: 100%;
background-color: #666;
z-index:4;
}
Here is the javascript:
var box1
var fader
window.onload = function() {
box1 = document.getElementById('box1');
fader = document.getElementsByClassName('fader');
}
function slide1(){
if(box1.style.width=='10%'){
box1.style.width='70%';
fader[0].style.opacity='1';
fader[0].style.transition='opacity 0.25s ease-in';
}
else {
box1.style.width='10%';
fader[0].style.opacity='0';
fader[0].style.transition='opacity 0.75s ease-in';
}
}
It's working for the fade-out, but for the fade-in it is immediately transitioning from 0 opacity to 1 opacity... there's no fade-in. Any ideas?
I actually asked a very similar question with the same issue a while back: Opacity effect works from 1.0 to 0, but not 0 to 1.0. Check the out and see if it works for you.
Otherwise, try adding a class to the fader element instead of adding a style declaration. Then, in your actual CSS, write the code for the fader element transition.
I guess you use even firefox or opera? I think your code won't work on safari or chrome since transition needs webkit-prefix on those browsers. You can use following code to get transition support:
var transform = (function () {
var transforms = [
'OTransform',
'MozTransform',
'msTransform',
'WebkitTransform'
], transform = 'transform';
while (transform) {
if (document.body.style[transform] === undefined) {
transform = transforms.pop();
} else {
return transform;
}
}
return false;
}());
When im using CSS-transition, sometimes I change transition style and then let browser update changes before changing other styles. You can do this with timeout. On some browsers I have noticed that animation is not working unless doing that (some firefox browsers).
fader[0].style.transition='opacity 0.75s ease-in';
setTimeout(function () {
box1.style.width='10%';
fader[0].style.opacity='0';
}, 4);