w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.42k stars 652 forks source link

[css-animations-1] Removing the target of an animation has different behaviour between CSS Animations, CSS Transitions and Web Animations #2948

Open graouts opened 6 years ago

graouts commented 6 years ago

I wrote three different tests that have different behaviour in various browsers. They all test what happens when you start an animation and then remove the animation's target and check the animation play state before and after.

css-animation.html

<body>
<style>

@keyframes anim {
    from { margin-left: 0 }
    to { margin-left: 100px }
}

div {
    animation: anim 1s;
}

</style>
<script type="text/javascript">

const target = document.body.appendChild(document.createElement("div"));
const animation = target.getAnimations()[0];

console.log(`playState before removing the target: ${animation.playState}`);
target.remove();
console.log(`playState after removing the target: ${animation.playState}`);

</script>
</body>

css-transition.html

<body>
<style>

div {
    transition: margin-left 1s;
}

</style>
<script type="text/javascript">

const target = document.body.appendChild(document.createElement("div"));

requestAnimationFrame(() => {
    target.style.marginLeft = "100px";
    const animation = target.getAnimations()[0];

    console.log(`playState before removing the target: ${animation.playState}`);
    target.remove();
    console.log(`playState after removing the target: ${animation.playState}`);
});

</script>
</body>

web-animation.html

<body>
<script type="text/javascript">

const target = document.body.appendChild(document.createElement("div"));
const animation = target.animate({ marginLeft: "100px" }, 1000);

console.log(`playState before removing the target: ${animation.playState}`);
target.remove();
console.log(`playState after removing the target: ${animation.playState}`);

</script>
</body>

Here are the results:

Test Firefox Nightly Chrome Canary WebKit ToT
css-animation.html running → idle pending → idle running → idle
css-transition.html running → idle pending → idle running → idle
web-animation.html running → running pending → pending running → idle

So it seems we have first some disagreements between browsers on what the initial state would be, Firefox and WebKit both thinking it's "running" and Chrome thinking it's "pending", although I assume this is just due to Chrome not having yet made the change to remove "pending" as one of the play states.

What I wonder about is why would removing the target of a CSS Animation or CSS Transition would result in the play state being "idle" for everyone, but not for a vanilla Web Animations. What spec says why that is the case?

@flackr @stephenmcgruer @birtles

birtles commented 6 years ago

I believe the distinction here is that for CSS animations and CSS transitions the animation is defined by style whereas for the API it is defined by script.

Last I checked, there was some mismatch about what the the spec and implementations do about getComputedStyle for elements that are not part of the document tree but the CSS Cascading spec seems to make it pretty clear they don't have computed values (see Value Processing). Certainly CSS transitions takes this interpretation:

Various things can cause the computed values of properties on an element to change. These include insertion and removal of elements from the document tree (which both changes whether those elements have computed values and can change the styles of other elements through selector matching) (3. Starting of transitions)

Similarly, since display:none omits the element from the box tree, those elements also won't have animations defined by the cascade as made explicit in CSS animations:

Setting the display property to none will terminate any running animation applied to the element and its descendants. If an element has a display of none, updating display to a value other than none will start all animations applied to the element by the animation-name property, as well as all animations applied to descendants with display other than none. (3. Animations)

Since animations defined by the API don't rely on the cascade to be defined, they can continue to run even when the element not included in the box tree. That is, in the absence of anything saying they don't run on such elements (unlike CSS animations/transitions), they should run.

graouts commented 6 years ago

What I wonder though is what procedure is expected to run as a result of an element with a style-originated animation no longer participating in the cascade. It seems to me that both Chrome and Firefox do something similar to calling cancel() on the animation, but I can't find spec text that makes this clear.

birtles commented 6 years ago

Right, that is the yet-to-be-written text for CSS Animations 2 and CSS Transitions 2. (There's an issue in CSS Transitions 2 specifically mentioning that this needs to be defined, but presumably we need to do the same for CSS Animations 2 as well.)