w3c / csswg-drafts

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

[css-animations-2][web-animations-2] Staggering children of effects inside GroupEffect #9561

Open ydaniv opened 8 months ago

ydaniv commented 8 months ago

This is a proposal for syntax that allows staggering of child effects in a GroupEffect by expanding the group-effect-align property proposed in #9554 by making it a shorthand of 2 properties, plus introducing 2 new values (TBB): effect-index and effect-count.

Description

The previously proposed group-effect-align property, for controlling synchronization of child effects inside the group, will become a shorthand of 2 sub properties: -type and -offset.

group-effect-align-type

This property behaves same as described here, with the addition of splitting the parallel behavior of "GroupEffect" to start and end, which aligns the child effects to either start or end together respectively. It has the following possible values:

group-effect-align-offset

This property allows specifying an additional delay offset that is added to to each child in the group. The extra delay offset is added either to the start delay, or the end delay if -type is set to end.

effect-index and effect-count

The purpose of the -offset property is mostly to enable staggering effects. To enable expressive staggering effects and reuse existing CSS math functions I propose adding 2 new identity values: effect-index and effect-count to reference the index of the child and count of children in the group respectively. If sequence or sequence-reverse are used, the -offset property can still apply by offsetting each child effect relative to its normal position.

Examples

/* constant offset */
group-effect-align: start 100ms;
/* constant stagger */
group-effect-align: start calc(100ms * effect-index);
/* eased stagger using log() assuming 1-based */
group-effect-align: start calc(100ms * log(effect-index));
/* flipping the above staggering order */
group-effect-align:  start calc(100ms * log(effect-count - effect-index + 1));
/* constant stagger from center */
group-effect-align: start calc(100ms * abs(effect-count / 2 - effect-index));
/* constant stagger from end */
group-effect-align: end calc(100ms * effect-index);
/* a log-decaying staggered sequence */
group-effect-align: sequence calc(-100ms * log(effect-index));

Proposed syntax

group-effect-align: <group-effect-align-type> || <group-effect-align-offset>
group-effect-align-type: start | end | sequence | sequence-reverse
group-effect-align-offset: <time>

I'm assuming there may be some pushback from developers expecting to see a "stagger" property. However, after reviewing various stagger implementations in some of the prominent animation libraries it seems that the API always implicitly assumes a form of index * <number> by simply allowing a number as a value, e.g. stagger: 0.1. Later this complicates the API further by having to indirectly specify other properties like "from", "ease", etc, which IMO are a bit implicit and "magical", and less suitable for a low-level web API. I think that by not assuming the above form we can achieve a more flexible and explicit API, which can better leverage existing CSS math functions and values.

birtles commented 8 months ago

Looks great. I think this approach allows the start times to be staggered using, e.g. a logarithmic function, without requiring the same sort of easing apply to the duration of each child animation.

That is, in some cases you'll want the whole scene to progressively get faster in which case you can apply easing to the group. That way the start times will get progressively closer together and the child durations will get progressively shorter.

In other cases, however, you want to maintain the same duration for each child animation but simply have their start times get progressively closer together.

I think the approach you've outlined allows both effects to be achieved.

Having part of the group-effect-align shorthand map to the parent group and part map to the children is a little odd, but we already do something similar with animations where part of the animation shorthand (animation-play-state) maps to the CSSAnimation object while the rest maps to the KeyframeEffect object.

Just to check, I believe you are proposing that group-effect-align-offset will be computed into the delay of the corresponding CSSAnimation objects' KeyframeEffects such that JS authors won't have to try to serialize/parse strings like start calc(100ms * log(effect-count - effect-index + 1)). To produce a similar stagger effect in JS, authors would use JS math functions to calculate the appropriate delays.

ydaniv commented 8 months ago

Thanks @birtles!

I think the approach you've outlined allows both effects to be achieved.

Yes, I didn't think about the group easing in this case, but yes, I think you could specify a value like the "static stagger" example above and the easing on top would take care of that.

Having part of the group-effect-align shorthand map to the parent group and part map to the children is a little odd

I didn't see it that way. I did follow your "flexbox" model here, so -type is used for justifying and -offset for extra margins. This could be odd, but these properties have no sense outside the context of a group, so specifying the offset independently losses that context, i.e. the context required for staggering.

Just to check, I believe you are proposing that group-effect-align-offset will be computed into the delay of the corresponding CSSAnimation objects' KeyframeEffects such that JS authors won't have to try to serialize/parse strings like start calc(100ms * log(effect-count - effect-index + 1))

The intent was to append the result of -offset per child to the child's own delay or endDelay correspondingly. So that if for some reason you specified both they would be respected. And yes, since staggering becomes very cumbersome when you need to specify delay per element, and also because it only becomes meaningful in the context of a group.

To produce a similar stagger effect in JS, authors would use JS math functions to calculate the appropriate delays.

Yes, most libraries allow a stagger function just for that. The intent is to replace that with calc(), math functions, and the effect-index and effect-count that should provide the missing arguments that these functions usually take.

Que-tin commented 8 months ago

Aren't effect-index and effect-count essentially duplicates to sibling-count() and sibling-index(). Most of the examples for those two were exactly what is stated here. I don't really see the difference at first glance.

ydaniv commented 8 months ago

@Que-tin effect-index and effect-count reference index and count of child effects inside an EffectGroup. They're values and not selectors. They don't reference DOM elements. I just used the "sibling" case as reference, as a convention.

ydaniv commented 6 months ago

Oops, I had a mistake above, sibling-*() functions are also values, not selectors, but they do refer to sibling elements and follow DOM structure, while effect-* values proposed above reference effects in the group.