javascript - Vad är det renaste sättet att tillfälligt inaktivera CSS-övergångseffekter?


Jag har ett DOM-element med denna effekt applicerad:

#elem {
  transition: height 0.4s ease;
}

Jag skriver ett jQuery-plugin som ändrar storlek på detta element, jag behöver inaktivera dessa effekter tillfälligt så att jag kan ändra storleken på det smidigt.

Vad är det mest eleganta sättet att tillfälligt inaktivera dessa effekter (och sedan aktivera dem igen), med tanke på att de kan tillämpas från föräldrar eller kanske inte tillämpas alls.

Svar #1

Kort svar

Använd detta CSS:

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

Plus antingen detta JS (utan 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

Eller detta JS med jQuery...

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

... eller motsvarande kod med vilket annat bibliotek eller ramverk du än arbetar med.

Förklaring

Detta är faktiskt ett ganska subtilt problem.

Först och främst vill du förmodligen skapa en 'notransition'-klass som du kan applicera på element för att sätta deras *-transition CSS-attribut till none. Till exempel:

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

Några mindre kommentarer om CSS innan vi går vidare:

  • Nu för tiden kanske du inte vill bry dig om leverantörsspecifika egenskaper som -webkit-transition, eller så kanske du har en CSS-förprocessare som lägger till dem åt dig. Att specificera dem manuellt var rätt sak att göra för de flesta webbappar när jag först publicerade detta svar 2013, men från och med 2023, enligt https://caniuse.com/mdn-css_properties_transition, använder endast ungefär 0,4 % av användarna i världen fortfarande en webbläsare som endast stöder en leverantörsspecifik version av transition.
  • Det finns inget som heter -ms-transition. Den första versionen av Internet Explorer som stödde övergångar överhuvudtaget var IE 10, som stödde dem utan prefix.
  • Detta svar antar att !important är tillräckligt för att denna regel ska åsidosätta dina befintliga stilar. Men om du redan använder !important på några av dina transition-regler, kanske det inte fungerar. I så fall kan du behöva göra someElement.style.setProperty("transition", "none", "important") för att inaktivera övergångarna (och lista ut själv hur du återställer den ändringen).

Hur som helst, när du försöker använda den här klassen kommer du att stöta på en fälla. Fällan är att kod som denna inte kommer att fungera på det sätt du kanske naivt förväntar dig:

// 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')

Naivt kan man tro att höjdändringen inte kommer att animeras, eftersom den sker medan klassen 'notransition' är tillämpad. I verkligheten, däremot, kommer den att animeras, åtminstone i alla moderna webbläsare jag har provat. Problemet är att webbläsaren buffrar de stiländringar som den behöver göra tills JavaScript har slutfört sin körning, och sedan utför alla ändringar i en enda "reflow". Som ett resultat utförs en reflow där det inte sker någon nettoförändring av huruvida övergångar är aktiverade eller inte, men det sker en nettoförändring av höjden. Följaktligen animeras höjdändringen.

Du kanske tycker att ett rimligt och rent sätt att komma runt detta skulle vara att lägga borttagningen av klassen 'notransition' i en 1 ms timeout, så här:

// 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);

men detta fungerar inte heller pålitligt. Jag kunde inte få koden ovan att krascha i WebKit-webbläsare, men i Firefox (på både långsamma och snabba maskiner) får man ibland (uppenbarligen slumpmässigt) samma beteende som när man använder det naiva tillvägagångssättet. Jag antar att anledningen till detta är att det är möjligt för JavaScript-exekveringen att vara tillräckligt långsam för att timeout-funktionen väntar på att köras när webbläsaren är ledig och annars skulle tänka på att göra en opportunistisk omflöde, och om det scenariot inträffar, kör Firefox den köade funktionen före omflödet.

Den enda lösningen jag har hittat på problemet är att tvinga en omflöde av elementet, vilket rensar de CSS-ändringar som gjorts på det, innan man tar bort klassen 'notransition'. Det finns olika sätt att göra detta - se här för några. Det närmaste man kommer en 'standard' metod för detta är att läsa elementets offsetHeight-egenskap.

En lösning som faktiskt fungerar, då, är

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

Här är en JS-fiddle som illustrerar de tre möjliga tillvägagångssätt jag har beskrivit här (både det framgångsrika tillvägagångssättet och de två misslyckade): http://jsfiddle.net/2uVAA/131/

Svar #2

Jag skulle förespråka att inaktivera animationer som föreslagits av DaneSoul, men göra bytet globalt:

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

.notransition kan sedan appliceras på body-elementet, vilket effektivt åsidosätter alla övergångsanimationer på sidan:

$('body').toggleClass('notransition');

Svar #3

Lägg till en extra CSS-klass som blockerar övergången och ta sedan bort den för att återgå till föregående tillstånd. Detta gör både CSS- och JQuery-koden kort, enkel och lättförståelig.

CSS:

.notransition {
  transition: none !important;
}

Observera: !important lades till för att vara säker på att denna regel får högre prioritet, eftersom att använda ett ID är mer specifikt än en klass.

JQuery:

$('#elem').addClass('notransition'); // to remove transition
$('#elem').removeClass('notransition'); // to return to previouse transition

Svar #4

För en ren JS-lösning (inga CSS-klasser), sätt bara transition till 'none'. För att återställa övergången som specificerat i CSS, sätt transition till en tom sträng.

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

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

Om du använder leverantörsprefix måste du också ställa in dem.

elem.style.webkitTransition = 'none'

Svar #5

Du kan inaktivera animation, övergångar, transformeringar för alla element på sidan med denna CSS-kod:

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);

Relaterade artiklar