How to Detect State of CSS Transition via Js and Skip It

How to detect state of CSS transition via JS and skip it

You can listen to transition event and remove it on demand:

const el = document.getElementById('transition');
let isAnimating = false;

el.addEventListener('transitionstart', function() {
isAnimating = true;
});

el.addEventListener('transitionend', () => {
isAnimating = false;
});

el.addEventListener('transitioncancel', () => {
isAnimating = false;
});

function removeTransition(checkIfRunning) {
if (checkIfRunning && !isAnimating) {
return;
}

el.style.transition = "none";
}
#transition {
width: 100px;
height: 100px;
background: rgba(255, 0, 0, 1);
transition-property: transform background;
transition-duration: 2s;
transition-delay: 1s;
}

#transition:hover {
transform: rotate(90deg);
background: rgba(255, 0, 0, 0);
}
<div id="transition">Hello World</div>
<br />
<button onclick="removeTransition(false)">Remove Transition</button>
<br />
<br />
<button onclick="removeTransition(true)">Remove Transition on if running</button>

What is the cleanest way to disable CSS transition effects temporarily?

Short Answer

Use this CSS:

.notransition {
transition: none !important;
}

Plus either this JS (without jQuery)...

someElement.classList.add('notransition'); // Disable transitions
doWhateverCssChangesYouWant(someElement);
someElement.offsetHeight; // Trigger a reflow, flushing the CSS changes
someElement.classList.remove('notransition'); // Re-enable transitions

Or this JS with jQuery...

$someElement.addClass('notransition'); // Disable transitions
doWhateverCssChangesYouWant($someElement);
$someElement[0].offsetHeight; // Trigger a reflow, flushing the CSS changes
$someElement.removeClass('notransition'); // Re-enable transitions

... or equivalent code using whatever other library or framework you're working with.

Explanation

This is actually a fairly subtle problem.

First up, you probably want to create a 'notransition' class that you can apply to elements to set their *-transition CSS attributes to none. For instance:

.notransition {
transition: none !important;
}

(Minor aside - note the lack of an -ms-transition in there. You don't need it. The first version of Internet Explorer to support transitions at all was IE 10, which supported them unprefixed.)

But that's just style, and is the easy bit. When you come to try and use this class, you'll run into a trap. The trap is that code like this won't work the way you might naively expect:

// Don't do things this way! It doesn't work!
someElement.classList.add('notransition')
someElement.style.height = '50px' // just an example; could be any CSS change
someElement.classList.remove('notransition')

Naively, you might think that the change in height won't be animated, because it happens while the 'notransition' class is applied. In reality, though, it will be animated, at least in all modern browsers I've tried. The problem is that the browser is caching the styling changes that it needs to make until the JavaScript has finished executing, and then making all the changes in a single reflow. As a result, it does a reflow where there is no net change to whether or not transitions are enabled, but there is a net change to the height. Consequently, it animates the height change.

You might think a reasonable and clean way to get around this would be to wrap the removal of the 'notransition' class in a 1ms timeout, like this:

// Don't do things this way! It STILL doesn't work!
someElement.classList.add('notransition')
someElement.style.height = '50px' // just an example; could be any CSS change
setTimeout(function () {someElement.classList.remove('notransition')}, 1);

but this doesn't reliably work either. I wasn't able to make the above code break in WebKit browsers, but on Firefox (on both slow and fast machines) you'll sometimes (seemingly at random) get the same behaviour as using the naive approach. I guess the reason for this is that it's possible for the JavaScript execution to be slow enough that the timeout function is waiting to execute by the time the browser is idle and would otherwise be thinking about doing an opportunistic reflow, and if that scenario happens, Firefox executes the queued function before the reflow.

The only solution I've found to the problem is to force a reflow of the element, flushing the CSS changes made to it, before removing the 'notransition' class. There are various ways to do this - see here for some. The closest thing there is to a 'standard' way of doing this is to read the offsetHeight property of the element.

One solution that actually works, then, is

someElement.classList.add('notransition'); // Disable transitions
doWhateverCssChangesYouWant(someElement);
someElement.offsetHeight; // Trigger a reflow, flushing the CSS changes
someElement.classList.remove('notransition'); // Re-enable transitions

Here's a JS fiddle that illustrates the three possible approaches I've described here (both the one successful approach and the two unsuccessful ones):
http://jsfiddle.net/2uVAA/131/

Bypassing transition and changing a property instantly

Since nobody else is posting a valid answer, here goes:

$('div').css('width', '200px').delay(1000).queue(function() {
$(this).css({transition: '0s', width: '10px'}).delay(1).queue(function() {
$(this).css({transition:'2s'});
});
},1000)​;

FIDDLE

Or if it's the other way:

$('div').css({
transition: '0s'
}).css('width', '200px').delay(1000).queue(function() {
$(this).css({width: '10px', transition: '2s'});
});

FIDDLE

jQuery should normalize vendor prefixes these days, so you don't have to type them all yourself.


The issue here is that jQuery attaches all the styles at once, only keeping the last styles, overwriting the previous styles of the same CSS property without ever doing a repaint of the DOM, and testing with native javascript seems to be doing the same thing, so it's probably the browser trying to avoid uneccessary reflows by adding a style just to have it changed in the next line of code, so doing:

$('div').css({
transition: '0s',
width: 200
}).css({
transition: '3s',
width: 10
});

won't work as only the last style is added.

This is where delay() comes into play, the OP's question was already using delay() so there was no reason not to use it, but removing delay() will of course cause the above issue, where the browser doesn't paint the first style, but only the last etc.

As delay() is really just a fancy timeout, it effectively defers the execution of the second setting of the styles, causing two browser repaints.

As this is most likely a browser issue, and not something we can change, deferring the setting of the second style is the only way to make this work, and using a delay will still work even if it's set to just 1 milliseconds, or one could defer the execution with a regular timeout, which is the usual way to defer execution of a script:

$('div').css({
transition: '0s',
width: 200
});

setTimeout(function() {
$('div').css({
transition: '3s',
width: 10
});
});

FIDDLE

The above will work just fine, as the timeout causes the first setting of the style to be painted by the browser, and defers the setting of the style inside the timeout to a later time, but as no time is set, it's executed as soon as the browser can (but still deferred until after the current script has completed), which for the human eye would seem like immediately, and that solves the issue.

Get the state of a CSS animation in JavaScript and print it inside an element or on the console so as to later manipulate it

Odd, I don't know if it's a browser bug or what.. but it seems that you can not, in fact, access that property of the element, even if it's explicitly assigned in the css.

getComputedStyle does seem to work though.

var myVar = setInterval(myTimer, 2000);
var marqueeText = document.getElementById('marqueeText');function myTimer() { var computedStyle = window.getComputedStyle(marqueeText); printState(computedStyle.animationPlayState); if(computedStyle.animationPlayState == "running"){ //doSomething(); }
}
function stopInterval(){ clearInterval(myVar); marqueeText.style.animationPlayState = "paused"; var computedStyle = window.getComputedStyle(marqueeText) printState(computedStyle.animationPlayState);}
function printState(state){ var animationState = document.getElementById('animationState'); console.log(state); animationState.innerHTML = state;}
@keyframes marquee    {        0%   { transform: translate(0%, 0); }        100% { transform: translate(-200%, 0);}    }
p { color:#000; margin-left: 100%; padding-inline-end: 50px; display: inline-block; white-space: nowrap; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 30pt; z-index: 10; animation: marquee 25s linear 0s 1; }
.animation{ width: 100%; background-color: darkblue; vertical-align: bottom; }
<div class='animationBackground'><p id="marqueeText">Scrolling Text Goes Here</p></div><div id="animationState">Animation State</div><button id='stop' type"button" onclick=stopInterval()>Stop Logging</button>

How do I pause CSS transition?

Transitions

A solution would be to remove the transition class and save the current state of styles (margin-leftin the example) when stopping. And when starting just add the transition class again.

var boxOne = document.getElementsByClassName('box')[0];var running = false;
var toggleTransition = function() { if(!running) { boxOne.classList.add('horizTranslate'); } else { var computedStyle = window.getComputedStyle(boxOne), marginLeft = computedStyle.getPropertyValue('margin-left'); boxOne.style.marginLeft = marginLeft; boxOne.classList.remove('horizTranslate'); }
running = !running;}
.box {  margin: 30px;  height: 50px;  width: 50px;  background-color: blue;}.box.horizTranslate {  -webkit-transition: 3s;  -moz-transition: 3s;  -ms-transition: 3s;  -o-transition: 3s;  transition: 3s;  margin-left: 50% !important;}
<div class='box'></div> <button class='toggleButton' onclick='toggleTransition()'>Toggle</button>

css transition doesn't work if element start hidden

To understand plainly the situation, you need to understand the relation between the CSSOM and the DOM.

In a previous Q/A, I developed a bit on how the redraw process works.

Basically, there are three steps, DOM manipulation, reflow, and paint.

  • The first (DOM manipulation) is just modifying a js object, and is all synchronous.
  • The second (reflow, a.k.a layout) is the one we are interested in, and a bit more complex, since only some DOM methods and the paint operation need it. It consists in updating all the CSS rules and recalculating all the computed styles of every elements on the page.

    Being a quite complex operation, browsers will try to do it as rarely as possible.
  • The third (paint) is only done 60 times per seconds at max (only when needed).

CSS transitions work by transitioning from a state to an other one. And to do so, they look at the last computed value of your element to create the initial state.

Since browsers do recalculate the computed styles only when required, at the time your transition begins, none of the DOM manipulations you applied are effective yet.

So in your first scenario, when the transition's initial state is calculated we have

.b { computedStyle: {display: none} }

... and that's it.

Because, yes, that's how powerful display: none is for the CSSOM; if an element has display: none, then it doesn't need to be painted, it doesn't exist.

So I'm not even sure the transition algorithm will kick in, but even if it did, the initial state would have been invalid for any transitionable value, since all computed values are just null.

Your .a element being visible since the beginning doesn't have this issue and can be transitioned.

And if you are able to make it work with a delay (induced by $.animate), it's because between the DOM manip' that did change the display property and the execution of this delayed DOM manip' that does trigger the transition, the browser did trigger a reflow (e.g because the screen v-sync kicked in between and that the paint operation fired).


Now, it is not part of the question, but since we do understand better what happens, we can also control it better.

Indeed, some DOM methods do need to have up-to-date computed values. For instance Element.getBoundingClientRect, or element.offsetHeight or getComputedStyle(element).height etc. All these need the entire page to have updated computed values so that the boxing are made correctly (for instance an element could have a margin pushing it more or less, etc.).

This means that we don't have to be in the unknown of when the browser will trigger this reflow, we can force it to do it when we want.

But remember, all the elements on the page needs to be updated, this is not a small operation, and if browsers are lenient to do it, there is a good reason.

So better use it sporadically, at most once per frame.

Luckily, the Web APIs have given us the ability to hook some js code just before this paint operation occurs: requestAnimationFrame.

So the best is to force our reflow only once in this pre-paint callback, and to call everything that needs the updated values from this callback.

$('button').on('click',function(){  $('.b').show(); // apply display:block synchronously    requestAnimationFrame(() => { // wait just before the next paint    document.body.offsetHeight; // force a reflow    // trigger the transitions    $('.b').css('right','80%');    $('.a').css('right','80%');  });})
body {  width:800px;  height:800px;}
div { width:50px; height:50px; background-color:#333; position:absolute; display:none; right:5%; top:0; transition:right .5s cubic-bezier(0.645, 0.045, 0.355, 1); color: white;}
.a { display:block; top:60px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><div class='a'>A</div><div class='b'>B</div><button>Launch</button>

Overwriting transitioned CSS property via JS makes transition stop working

The reason for not working is because pad.style.background will add an inline css style which has a priority over a css class

Solution:

use a class instead of inline style like in the code bellow:

let pad = document.getElementsByClassName('pad')[0]pad.classList.add("green");
.pad {  width: 80px;  height: 80px;  background: black;  transition: background .5s;}
.pad.green { background: green;}
.pad:active { background: yellow;}
<div class="pad"></div>


Related Topics



Leave a reply



Submit