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

12

262

I have a DOM element with this effect applied:

#elem {
  transition: height 0.4s ease;
}

I am writing a jQuery plugin that is resizing this element, I need to disable these effects temporarily so I can resize it smoothly.

What is the most elegant way of disabling these effects temporarily (and then re-enabling them), given they may be applied from parents or may not be applied at all.

Inseminate answered 21/6, 2012 at 5:16 Comment(0)
D
593

Short Answer

Use this CSS:

.notransition {
  -webkit-transition: none !important;
  -moz-transition: none !important;
  -o-transition: none !important;
  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 {
  -webkit-transition: none !important;
  -moz-transition: none !important;
  -o-transition: none !important;
  transition: none !important;
}

Some minor remarks on the CSS before moving on:

  • These days you may not want to bother with the vendor-prefixed properties like -webkit-transition, or may have a CSS preprocessor that will add them for you. Specifying them manually was the right thing to do for most webapps when I first posted this answer in 2013, but as of 2023, per https://caniuse.com/mdn-css_properties_transition, only about 0.4% of users in the world are still using a browser that supports only a vendor-prefixed version of transition.
  • There's no such thing as -ms-transition. The first version of Internet Explorer to support transitions at all was IE 10, which supported them unprefixed.
  • This answer assumes that !important is enough to let this rule override your existing styles. But if you're already using !important on some of your transition rules, that might not work. In that case, you might need to instead do someElement.style.setProperty("transition", "none", "important") to disable the transitions (and figure out yourself how to revert that change).

Anyway, 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 buffering 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/

Danelaw answered 15/5, 2013 at 22:8 Comment(16)
Excelente answer. Good job, and appreciated since I came up with the same problem. What if I wanted to remove only one type of transition?Consortium
Your solution is right, but for those looking for more background information: https://mcmap.net/q/37928/-clean-way-to-programmatically-use-css-transitions-from-jsCompartmentalize
Minor minor aside, IE10 was the first stable version of IE to ship with unprefixed transitions. The pre-release versions, excluding the Release Preview, required the -ms- prefix for transitions, along with a slew of other things. That, I suspect, is one of the reasons the -ms- prefix appears today. The other, much more likely of course, reason is the usual cargo cult that we've all come to know and love about vendor prefixes.Twickenham
Interesting that simply checking the offset height of an element triggers a reflow. Wish I knew this a year ago when I was banging my head against the wall with this same problem. Is this still considered the most ideal way of handling this?Unshroud
But this won't work for :before and :after because you can't add classes for pseudo-elements. I do .notransition, .notransition:before, .notransition:after {transition: none !important;} and add class for element itself. But if you need to disable only pseudo-element's animation but not element's I'd recomend to add different classes for element e.g. .notransition, .notransition-before:before, .notransition-after:after {transition: none !important;}Pirozzo
The reflow trick doesn't seem to work when animating SVG elements which don't have an offsetHeight attribute; setTimout works.Alvinaalvine
Thanks, I was struggling with the cached styles / reflow issue as well. offsetHeight seems like a hack, but it works fine. Would be nice if there was a specific reflow() function we could call.Handyman
@Alvinaalvine setTimeout works for removing the transition on the SVG element in my function but breaks a bunch of other stuff in my function for some reason...Olenta
focus() works, but breaks the function in contexts where the SVGs aren't in the DOM... gah these hacks ;(...Olenta
Well, I just used jQuery to check if the SVG is in the DOM then run the .focus() 'reflow' hack. It's ugly but it works.Olenta
The reflow trick did not work for me. On DOM ready event, I determine the max-width with window.matchMedia and want to hide the vertical navigation on mobile device by toggling classes. There is a transition for the sidebar which is supposed to be skipped if it wasn't user initiated. Accessing offsetHeight didn't help in Chrome 74. I use setTimeout() instead as a workaround. The delay is equal to the animation duration just to be safe.Moleskins
@CoDEmanX Does the reflow trick fail for you even in my JSFiddle, or just on your webpage? If the latter, I'd at least take a look at a demo if you were to post a link here to one (either on JSFiddle etc. or in a Stack Overflow question if you're up to that) and try to figure out what's going wrong and whether it's fixable.Danelaw
It seems to work in the fiddle. Do I need to call offsetHeight on a specific element? I tried the element which I add the notransition class to as well as the document object to no avail. What seems to work is calling jQuery's offset() or focus() method on the element in question. I wonder what makes the difference...Moleskins
@CoDEmanX In the fiddle, I access the offfsetHeight of the container I'm adding the notransition class to. I don't know the detail of how reflows work well enough to know if doing it on a parent or sibling or child would work. Again, I'd be interested to see a demo so I can play with it and try to learn something (and perhaps improve this answer). Just knowing that you have encountered a case where this didn't work (or at least think you have - no offence, but I won't be sure until I have a demo in front of me) isn't much use if I can't play around and make sense of what's going on.Danelaw
@Consortium You can "remove" the transition for any one property by setting its transition speed to 0s. This can be done with purely CSS. For example: transition: all 0.5s, border 0s;Swerve
Don't use !important unless you absolutely have to. it can cause A LOT OF headaches later. I've tried this without it and as long as you follow CSS's cascade rules it still works.Semicolon
O
24

I would advocate disabling animation as suggested by DaneSoul, but making the switch global:

/*kill the transitions on any descendant elements of .notransition*/
.notransition * { 
  transition: none !important; 
} 

.notransition can be then applied to the body element, effectively overriding any transition animation on the page:

$('body').toggleClass('notransition');
Overact answered 21/6, 2012 at 6:45 Comment(6)
This is the right answer for situations where you just want to do all at once. For instance, I'm wanting to disable transitions while rotating on mobile and this is perfect for that.Sealer
I ended up using this in angularjs, in a somewhat complicated scenario, but by god this was so helpful after days of banging my head.Frieze
Performance wise I cannot expect this to be a good idea at all. "Oh, just apply this to EVERYTHING on the page, that makes things easier!"Duff
@Lodewijk: Toggling a class is cheaper on a single element than on multiple (in terms of DOM overhead), but ultimately this boils down to convenience and cost of writing/maintaining code. Universal selectors certainly have their use, from numerous resets to the glorious box-sizing... ftw. You'd be surprised how performant CSS processing has become, even for the rule above; in fact they've removed CSS selector profiler from Chrome to discourage premature optimization.Overact
Should probably select both ".notransition" and ".notransition *" to be fully effective.Mansuetude
I recommend not using !important unless you absolutely have to. it can cause A LOT OF headaches later. I've tried this without it and as long as you follow CSS's cascade rules it still works fine.Semicolon
T
22

Add an additional CSS class that blocks the transition, and then remove it to return to the previous state. This make both CSS and JQuery code short, simple and well understandable.

CSS:

.notransition {
  transition: none !important;
}

Note: !important was added to be sure that this rule will have higher preference, because using an ID is more specific than class.

JQuery:

$('#elem').addClass('notransition'); // to remove transition
$('#elem').removeClass('notransition'); // to return to previouse transition
Tepee answered 21/6, 2012 at 5:31 Comment(2)
-1 because this wasn't sufficient to solve the problem for me in Chrome or Firefox - if I have Javascript that adds the class, makes the changes, and removes the class, sometimes the changes still animate. I've spent the last hour or two studying this problem in some detail and will post an answer later with fiddles showing cases where the naive approach fails, plus a neat hack that fixes the solution here by forcing a reflow between making the changes and removing the 'notransition' class.Danelaw
I recommend not using !important unless you absolutely have to. it can cause A LOT OF headaches later. I've tried this without it and as long as you follow CSS's cascade rules it still works fine.Semicolon
M
16

For a pure JS solution (no CSS classes), just set the transition to 'none'. To restore the transition as specified in the CSS, set the transition to an empty string.

// Remove the transition
elem.style.transition = 'none';

// Restore the transition
elem.style.transition = '';

If you're using vendor prefixes, you'll need to set those too.

elem.style.webkitTransition = 'none'
Mcmullan answered 14/5, 2015 at 1:49 Comment(2)
this is the simplest solution. I don't know why those answer create a new class ? do they like typing more code ?Implode
@Siwei, this is perfectly useful on a single element. If you have a bunch of elements having transitions, as I do in an image grid, it's less useful. Hence the tendency to make it reusable with class toggling.Trollope
B
14

You can disable animation, transition, transforms for all of element in page with this CSS code:

var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '* {' +
'  transition-property: none !important;' +
'  transform: none !important;' +
'  animation: none !important;}';

document.getElementsByTagName('head')[0].appendChild(style);
Bellarmine answered 7/10, 2015 at 9:12 Comment(2)
Firstly this is awesome, thank you! Secondly there are also some other properties would should be set; see github.com/japgolly/test-state/blob/master/util/shared/src/test/…Connel
@Connel Those other properties could affect the final designKelley
W
2

I think you could create a separate CSS class that you can use in these cases:

.disable-transition {
  transition: none;
}

Then in jQuery you would toggle the class like so:

$('#<your-element>').addClass('disable-transition');
Weariless answered 21/6, 2012 at 5:30 Comment(2)
yes I think this is the best approach, in fact the plugin can inject that css block into the pageInseminate
Are you sure without !important CLASS ruler will override ID one?Tepee
P
1

If you want a simple no-jquery solution to prevent all transitions:

  1. Add this CSS:
body.no-transition * {
  transition: none !important;
}
  1. And then in your js:
document.body.classList.add("no-transition");

// do your work, and then either immediately remove the class:

document.body.classList.remove("no-transition");

// or, if browser rendering takes longer and you need to wait until a paint or two:

setTimeout(() => document.body.classList.remove("no-transition"), 1);

// (try changing 1 to a larger value if the transition is still applying)
Pepsin answered 21/6, 2012 at 5:16 Comment(1)
I added an animation: none !important; as well, but this does the trick. Very handy for managing prefers-motion-reduced.Waste
F
1

This is the workaround that worked easily for me. It isn't direct answer to the question but still may help someone.

Rather than creating notransition class which was supposed to cancel the transition

.notransition {
  -webkit-transition: none !important;
  -moz-transition: none !important;
  -o-transition: none !important;
  transition: none !important;
}

I created moveTransition class

.moveTransition {
      -webkit-transition: left 3s, top 3s;
      -moz-transition: left 3s, top 3s;
      -o-transition: left 3s, top 3s;
      transition: left 3s, top 3s;
}

Then I added this class to element with js

element.classList.add("moveTransition")

And later in setTimeout, I removed it

element.classList.remove("moveTransition")

I wasn't able to test it in different browsers but in chrome it works perfectly

Farleigh answered 26/7, 2020 at 18:2 Comment(0)
I
0

If you want to remove CSS transitions, transformations and animations from the current webpage you can just execute this little script I wrote (inside your browsers console):

let filePath = "https://dl.dropboxusercontent.com/s/ep1nzckmvgjq7jr/remove_transitions_from_page.css";
let html = `<link rel="stylesheet" type="text/css" href="${filePath}">`;
document.querySelector("html > head").insertAdjacentHTML("beforeend", html);

It uses vanillaJS to load this css-file. Heres also a github repo in case you want to use this in the context of a scraper (Ruby-Selenium): remove-CSS-animations-repo

Intemperance answered 25/10, 2019 at 13:2 Comment(0)
S
0

Maybe not exactly what the OP wants, but if you want to kill all currently running animations programmatically, for example in some event handler (React, Angular, etc.), you could do this:

document.getAnimations().forEach((animation) => {
   animation.cancel();
});

Adding and then removing "animation suppressing" classes did not work for me. The animation started again as soon as I removed the suppressing class.

See Mozilla docs for more info

Subsumption answered 6/10, 2023 at 19:13 Comment(0)
C
-2

does

$('#elem').css('-webkit-transition','none !important'); 

in your js kill it?

obviously repeat for each.

Californium answered 21/6, 2012 at 5:25 Comment(1)
then you need to reset it after the fact, so you need to store it, which would lead to a fair amount of boiler plateInseminate
B
-3

I'd have a class in your CSS like this:

.no-transition { 
  -webkit-transition: none;
  -moz-transition: none;
  -o-transition: none;
  -ms-transition: none;
  transition: none;
}

and then in your jQuery:

$('#elem').addClass('no-transition'); //will disable it
$('#elem').removeClass('no-transition'); //will enable it
Batfish answered 21/6, 2012 at 5:31 Comment(3)
Are you sure without !important CLASS ruler will override ID one?Tepee
Yes, it will because now you're targeting #elem.no-transition which is more specific than just the idBatfish
@MoinZaman Erm, but you haven't targeted #elem.no-transition in your CSS, you've just targeted .no-transition. Perhaps you meant to write #elem.no-transition in your CSS?Danelaw

© 2022 - 2024 — McMap. All rights reserved.