Can I limit a CSS animation to play only once when

2019-07-10 02:59发布

问题:

Basically I'm trying to show an animation on certain elements that were affected in a "most recent action". After this animation completes it shouldn't play again. The problem I have at the moment is that I also define an animation which runs when the element(s) are "selected". When the element is deselected the original animation plays again, even if the animation-iteration-count of 1 has been reached. I could certainly set up an event listener for when animationend fires and then remove the class associated with the animation. I'm more trying to reason about why this doesn't work as intended. I guess this is sort of the opposite of this question.

document.querySelector('.one-time-animation').addEventListener("click", function(e) {
	e.target.classList.toggle('selected');
})
.one-time-animation {
    animation: one-time-animation 2s forwards 1;
}

.selected {
    animation: selected 2s forwards 1;
}

@keyframes one-time-animation {
    from {
        background:red;
    }
    to {
        background:transparent;
    }
}

@keyframes selected {
    from {
        box-shadow: 0 0 0 green inset;
    }
    to {
        box-shadow: 0 0 10px green inset;
    }
}
<div class="one-time-animation">Click to toggle selected class</div>

回答1:

Pure CSS Solution: (without polluting the selected style)

Assuming the animation provided in question is the exact same used in your application and not just some random sample, you could actually replace it with a transition instead of an animation. This would also make sure that the original animation is never removed and hence never replayed.

document.querySelector('.one-time-animation').addEventListener("click", function(e) {
  e.target.classList.toggle('selected');
})
.one-time-animation {
  animation: one-time-animation 2s forwards 1;
}
@keyframes one-time-animation {
  from {
    background: red;
  }
  to {
    background: transparent;
  }
}

/* do the following changes */

.selected {
  box-shadow: 0 0 10px green inset;
  transition: box-shadow 2s ease;
}

/* the below is generally not required but if the box-shadow just appears instantly instead of gradually then uncomment it
div {
  box-shadow: 0 0 0 green inset;
}
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class="one-time-animation">Click to toggle selected class</div>


Reason:

The animations are working as they are supposed to be based on the specifications. On click, when the selected class is added to the element, it no longer has one-time-animation. This is because the latest specified value for the animation property over-rides the previous specified one (as rightly pointed out by shennan).

When the selected class is toggled off, the one-time-animation becomes applicable and by now the user-agent has no remembrance of this animation having been present on the element earlier. This results in the animation getting re-executed even though the animation-iteration-count is set to 1.

One way to fix this issue would be to let the user agent know that the original animation is never really removed from the element. This can be performed by retaining it as part of the stack in the selected class. Animation property can support multiple animations in a comma separated format. The original animation doesn't run again because the user agent now treats it as ever present and thus maintains the iteration counter.

document.querySelector('.one-time-animation').addEventListener("click", function(e) {
  e.target.classList.toggle('selected');
})
.one-time-animation {
  animation: one-time-animation 2s forwards 1;
}
.selected {
  animation: one-time-animation 2s forwards 1, selected 2s forwards 1;
}
@keyframes one-time-animation {
  from {
    background: red;
  }
  to {
    background: transparent;
  }
}
@keyframes selected {
  from {
    box-shadow: 0 0 0 green inset;
  }
  to {
    box-shadow: 0 0 10px green inset;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class="one-time-animation">Click to toggle selected class</div>

If it is not possible to add the original animation as part of the stack for selected class (say because it is used for multiple elements each of which have different original animations) then the approach should be to get the current animation, append the selected animation to it and then set it via JS.

I am not a JS expert but the below snippet should give you an idea of what I mean.

var i = false;

document.querySelector('.one-time-animation').addEventListener("click", function(e) {
  var style = window.getComputedStyle(e.target);
  var currAnim = style.getPropertyValue(PrefixFree.prefix + 'animation-name') + ' ' + style.getPropertyValue(PrefixFree.prefix + 'animation-duration') + ' ' + style.getPropertyValue(PrefixFree.prefix + 'animation-fill-mode') + ' ' + style.getPropertyValue(PrefixFree.prefix + 'animation-iteration-count');
  var newAnim;
  if (!i)
    newAnim = currAnim + ', selected 2s forwards 1;';
  else
    newAnim = currAnim.substring(0, currAnim.indexOf(','));
  e.target.setAttribute('style', PrefixFree.prefix + 'animation: ' + newAnim);
  i = !i;
});
.one-time-animation {
  animation: one-time-animation 2s forwards 1;
}
@keyframes one-time-animation {
  from {
    background: red;
  }
  to {
    background: transparent;
  }
}
@keyframes selected {
  from {
    box-shadow: 0 0 0 green inset;
  }
  to {
    box-shadow: 0 0 10px green inset;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class="one-time-animation">Click to toggle selected class</div>



回答2:

I believe the animation-iteration-count is reset when it is re-applied due to your toggling the select class. This is due to the cascading nature of CSS.

One option would be to simply remove the one-time-animation class when you go to select it:

document.querySelector('.selectable').addEventListener("click", function(e) {
    e.target.classList.remove('one-time-animation');
    e.target.classList.toggle('selected');
})
.one-time-animation {
    animation: one-time-animation 2s forwards 1;
}

.selected {
    animation: selected 2s forwards 1;
}

@keyframes one-time-animation {
    from {
        background:red;
    }
    to {
        background:transparent;
    }
}

@keyframes selected {
    from {
        box-shadow: 0 0 0 green inset;
    }
    to {
        box-shadow: 0 0 10px green inset;
    }
}
<div class="selectable one-time-animation">Click to toggle selected class</div>