CSS Animations - Change a Property Without a Transition

How to change a property in a CSS animation abruptly without animating

As I had mentioned in my comment, there is no way to achieve this with just a single animation. I wouldn't call your original approach as hacky because a sudden change means the addition of a new keyframe immediately after the previous one (50% and 51%) but I kind of get what you mean to say.

One possible alternate is to make use of two animations - one for the transform and the other for the transform-origin change. Here we can make the second animation (the transform-origin change) alone have a steps (or step-end) timing function so that this change alone happens abruptly.

(Note: I'm providing this option just as an answer to your question. I'd still prefer the earlier approach and avoid having two different keyframe rules to perform the same animation.)

Below is a sample snippet:

.image {  position: absolute;  top: 50%;  left: 50%;  width: 120px;  height: 120px;  margin: -60px 0 0 -60px;  animation: spin 4s linear infinite, origin 4s step-end infinite;  transform-origin: 50% 0;}@keyframes spin {  0% {    transform: perspective(800px) rotateX(0deg) translateZ(0px);  }  50% {    transform: perspective(800px) rotateX(360deg) translateZ(0px);  }  100% {    transform: perspective(800px) rotateX(0deg) translateZ(0px);  }}@keyframes origin {  50% {    transform-origin: 50% 100%;  }}
<img class="image" src="http://makeameme.org/media/templates/120/grumpy_cat.jpg" alt="Sample Image" width="120" height="120">

CSS animations without gradual and with instant change of properties

.move-me {  display: inline-block;  padding: 20px;  color: white;  position: relative;  margin: 0 0 10px 0;}.move-me-1 {  animation: move-in-steps 8s steps(4) infinite;}.move-me-2 {  animation: move-in-steps 8s steps(4, start) infinite;}.move-me-3 {  animation: move-in-steps 8s infinite;}
body { padding: 20px;}
@keyframes move-in-steps { 0% { left: 0; background: blue; } 100% { left: 100%; background: red; }}
<div class="move-me move-me-1">steps(4, end)</div><br><div class="move-me move-me-2">steps(4, start)</div><br><div class="move-me move-me-3">no steps</div>

Keyframe Animation with no transition

Unfortunately we can't animate the display property, i've changed to opacity, check the snippet:

* {
margin: 0;
padding: 0;
font-family: 'Press Start 2P', cursive;
overflow: hidden;
user-select: none;
}

#startScreen {
background: #000;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100vh;
text-align: center;
}

#startScreen h1 {
font-size: 50px;
margin: 1em;
animation: 1s flickerText infinite;
animation-timing-function: step-end;
}

#startScreen #startGame {
background: #000;
color: #fff;
padding: 1em;
border: 1px solid #fff;
cursor: pointer;
}

@keyframes flickerText {
0% { opacity: 0; }
50% { opacity: 1; }
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">

<div id="startScreen">
<h1>Welcome To Typer Writer!</h1>
<button id="startGame">Start New Game!</button>
</div>

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

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

(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/

CSS transition animate without presetting property value

Properties that have a numeric default can be animated without first instantiating their value.

The default of top is top: auto; (source), therefore you must declare a numeric value for it before animating, as CSS doesn't know how to transition from auto to number.

opacity on the other hand has a default of opacity: 1 (source), therefore you can animate it without first setting it's value.

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.

Is it possible to create css animation without transitions?

NO TRICKS NEEDED

The right way of doing this is through the transition-timing-function property, that defines the effect between the steps of the animation.

One of the values is step-end, that skips the animation step to the end result, so:

-webkit-animation: play 5s step-end;
animation: play 5s step-end;

http://jsfiddle.net/oe5nfy2L/1/

Transitions on the CSS display property

You can concatenate two transitions or more, and visibility is what comes handy this time.

div {  border: 1px solid #eee;}div > ul {  visibility: hidden;  opacity: 0;  transition: visibility 0s, opacity 0.5s linear;}div:hover > ul {  visibility: visible;  opacity: 1;}
<div>  <ul>    <li>Item 1</li>    <li>Item 2</li>    <li>Item 3</li>  </ul></div>


Related Topics



Leave a reply



Submit