6
votes

I have been trying to put some basic CSS3 animation. The objective is to toggle a class on the click event of a button and animate a div based on the added class. The code works perfectly for the first iteration of toggle in Firefox but for other browsers like Chrome and for the next iteration in Firefox the transformation is toggled in a blink of an eye. Please help me to figure out what's going wrong.

Snippet:

$('button').click(function() {
  $('div').toggleClass('clicked');
});
div {
  background-color: #ccc;
  height: 100px;
  width: 100px;
  transition-property: top, left;
  transition-duration: 1s;
  transition-timing-function: linear;
  position: relative;
  top: 0;
  left: 0;
}
.clicked {
  animation-name: clicked;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}
@keyframes clicked {
  0% {
    top: 0;
    left: 0;
  }
  100% {
    top: 100px;
    left: 100px;
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button type="button">Click Me!</button>
<div></div>

I have also prepared a fiddle here.

2
I'm using Chrome on Ubuntu 15.04 and it seems to be working for me. If I click the button once, the animation starts. If I click the button again, the div goes back to its original position. Then, if I click the button again, the animation starts again and so on and so on.Noble Mushtak
Do you really need the animation? The movement could be achieved even without it is a linear motion. I have seen the transition issue with Chrome earlier. You can refer to this thread for some related info - stackoverflow.com/questions/31806649/… (I am linking it only because we found that transition doesn't quite work well in Chrome when animation is removed - this is mentioned in the question there also).Harry
@Mushtak: I am using Windows 8.1, the animation works for Firefox only once, from next iteration the animation is not smooth as I mentioned and For Chrome/Opera the problem occurs in first iteration.Rivnat
If you are OK with avoiding the animation, you can achieve the same effect without it like here.Harry
@Harry: Thank you for your reference link and the fiddle. That is what I was looking for.Rivnat

2 Answers

6
votes

This is sort of a known behavior with Chrome. Firefox does seem to be able to handle the removal of animation smoothly with transition but Chrome doesn't do so. I had seen this behavior happen earlier also in this thread.

Why does removal of an animation not work with transition in Chrome?

While I cannot provide a 100% fool-proof explanation of why this happens, we can decode it to some extent based on this HTML5Rocks article about Accelerated rendering in Chrome and this one about GPU accelerated compositing in Chrome.

What seems to happen is that the element gets its own rendering layer because it has explicit position property set on it. When a layer (or part of it) gets invalidated due to animation, Chrome only repaints that layer which is affected by the change. When you open the Chrome Developer Console, switch on "Show Paint Rects" option, you would see that when the animation is happening Chrome only paints the actual element that is getting animated.

However, at the start and end of animation a whole page repaint is happening which puts the element back into its original position immediately and thus overriding the transition behavior.

$('button').click(function(){
  $('div').toggleClass('clicked');
});
div{
  background-color: #ccc;
  height: 100px;
  width: 100px;
  transition-property: top, left;
  transition-duration: 1s;
  transition-timing-function: linear;
  position: relative;
  top: 0;
  left: 0;
}
.clicked{
  animation-name: clicked;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}
@keyframes clicked{
  0% {top: 0; left: 0;}
  100% {top: 100px; left: 100px;}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button type="button">Click Me!</button>
<div></div>

What is the solution?

Since your movement is actually a linear movement from one position to another, you can achieve it without the need for any animation. All that we need to do is use a translate transform and shift the element by the required no. of pixels when the class is toggled on. Since there is a transition assigned to the element via another selector, shifting would happen in a linear way. While class is toggled off, the element moves back to its original position again in a linear way due to transition on the element.

$('button').click(function() {
  $('div').toggleClass('clicked');
});
div {
  background-color: #ccc;
  height: 100px;
  width: 100px;
  transition-property: transform;
  transition-duration: 1s;
  transition-timing-function: linear;
  position: relative;
  top: 0;
  left: 0;
}
.clicked {
  transform: translate(100px, 100px);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<button type="button">Click Me!</button>
<div></div>
4
votes

It is an expected behavior. You need to use two separate animations or stick with the transition only.

A transition is an animation, just one that is performed between two distinct states - i.e. a start state and an end state. Like a drawer menu, the start state could be open and the end state could be closed, or vice versa.

If you want to perform something that does not specifically involve a start state and an end state, or you need more fine grain control over the keyframes in a transition, then you've got to use an animation.

This is the way to animate the element with transition:

$('button').click(function() {
  $('div').toggleClass('clicked');
});
div {
  background-color: #ccc;
  height: 100px;
  width: 100px;
  transition-property: top, left;
  transition-duration: 1s;
  transition-timing-function: linear;
  position: relative;
  top: 0;
  left: 0;
}
.clicked {
  top: 100px;
  left: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button type="button">Click Me!</button>
<div></div>