Repository: w3c/csswg-drafts

Issue #6398: [css-animations][css-transitions] Isolate animation side-effects?
Created: 2021-06-21T13:45:34Z
(Using "animations" loosely to refer to any of CSS/Web Animations/Transitions/):
There seems to be an underlying idea in the specifications that effects caused by animations should themselves not trigger new animations. We solve this by making things "not animatable", "animation-tainted", etc. With container queries this problem becomes a bit worse, since the animated size of some element can cause the size of a sibling container (for example) to re-evaluate its container query, hence applying a totally different style to its descendants, including new animation- properties. Probably we need some adjustment to the specs to define what should happen when such animation properties appear (or change) because of "animated re-evaluation" of container queries.
The css-transitions spec describes some alternate-reality-styles (before-change style and after-change style) which is used to determine from/to values. This does give a behavior for container queries, but the before/after-change styles now implicitly become before/after-change layouts. The before-change style is particularly painful, since we would need to do layout in a world where existing animations are updated to the current time, producing different animation effects, potentially causing a change in container query evaluation results, arbitrarily modifying the "previous" style and layout. This sounds pretty complicated to me, and I thought we should explore alternatives. And even if we want to keep css-transitions as is, we probably do need some update for css-animations.
What if we isolate all side-effects that happen because of stuff that's animating, such that it can't trigger new animations:

Let the base style be the style as it would be without any animation effects applied (of any kind), inheriting from the base style of the parent.
Let the before-change style be the base style as of the previous style change event, inheriting from the before-change style of the parent. (I.e. just remember the previous base style).
Let the after-change style be the base style as of the current style change event, inheriting from the after-change style of the parent.
CSS transitions are triggered using the before-change style/after-change style. (Taking into account the current effect value for re-targeting purposes. The spec already covers that, though).
CSS animations are triggered using the after-change style.
Let the animated style be the style as of the current style change event, including all animation effects, but taking the computed values of the animation- and transition- properties from the after-change-style.
The animation-/transition- properties are ignored on the animated style, hence if they appear/disappear/update because of container queries re-evaluating, then nothing happens. ("Global animation-tainting"). (EDIT: Take the computed values from after-change style instead, see previous bullet point).

Not sure how web-compatible this is, or if this loses us any crucial aspect of the current specified behavior. On the other hand, the interop situation seems pretty poor in this area, and no browser seems to implement the current before/after change style correctly, so there might be wiggle-room.
At the moment I don't have any other great ideas for how to deal with animations and container queries.

Duplicate issue #8701: [css-transitions] starting an animation and a transition with equal values yields some incompatible behavior between Safari, Chrome and Firefox
Created: 2023-04-11T14:39:21Z
If I have an element specified as follows:
div {
    width: 0;
    height: 100px;
    background-color: black;
    transition: width 250ms linear;
}
… and later on set the following styles:
div.animated {
    width: 1000px;
    animation: grow 1s forwards linear;
}

@keyframes grow {
    from { width: 0 }
    to   { width: 1000px }
}
… then what is the expected behavior with regards to transition during the lifetime of the explicit CSS Animation started when the animated class is applied? I'm observing a different behavior in each of Chrome Canary, Firefox Nightly and Safari Technology Preview. In all three browsers a CSSAnimation is yielded going from width: 0px to width: 1000px as expected. However, the behavior differs greatly for the transition:

Chrome Canary (114.0.5708.0): no transition is ever started (no transitionstart event and no CSSTransition object visible when calling getAnimations() right after applying the animated class).
Firefox Nightly (114.0a1 (2023-04-11)): a transition going from width: 0px to width: 1000px is started as the animated class is applied and runs for 400ms until its natural completion.
Safari Technology Preview (167): same behavior as Firefox when the animated class is applied, however the transition is canceled and restarted on each frame.

While this does not yield a visible difference because the CSS Animation overrides the CSS Transition, this is still an observable difference through events and getAnimations().
Here's the complete test (sadly GitHub does not allow attaching an HTML file):
<!DOCTYPE html>
<style>

div {
    width: 0;
    height: 100px;
    background-color: black;

    transition: width 250ms linear;
}

div.animated {
    width: 1000px;
    animation: grow 1s forwards linear;
}

@keyframes grow {
    from { width: 0 }
    to   { width: 1000px }
}

</style>

<div></div>

<script>

const div = document.querySelector("div");

const currentWidth = () => getComputedStyle(div).width;

const dumpAnimations = label => {
    const animations = document.getAnimations().map(animation => {
        const keyframes = animation.effect.getKeyframes();
        return `${animation.constructor.name} from ${keyframes[0].width} to ${keyframes[1].width}`;
    });
    console.log(`${label} width is ${currentWidth()} and getAnimations() yields [${animations.join(', ')}]`);
}

const dumpTransitionEvent = event => dumpAnimations(`After ${event.type} fired`);
div.addEventListener("transitionstart", dumpTransitionEvent);
div.addEventListener("transitioncancel", dumpTransitionEvent);
div.addEventListener("transitionend", dumpTransitionEvent);

requestAnimationFrame(() => {
    div.classList.add("animated");
    dumpAnimations("After starting animations");
    requestAnimationFrame(() => dumpAnimations("One frame after starting animations"));
    setTimeout(() => dumpAnimations("100ms after starting animations"), 100);
    setTimeout(() => dumpAnimations("500ms after starting animations"), 500);
});

</script>
Cc people I know to have worked or are working on animations in the affected browsers: @flackr, @BorisChiou and @birtles.

Closing comment (2023-04-11T15:01:47Z): Also cc @andruud (and noting that this is related to #6398 and #6688).