csswg-drafts icon indicating copy to clipboard operation
csswg-drafts copied to clipboard

[css-animations-2] Composed Animations with existing keyframes

Open bramus opened this issue 1 year ago • 7 comments

This is a split-off from https://github.com/w3c/csswg-drafts/issues/7044 where it was suggested to allow authors to use existing keyframes as part of a larger set of keyframes.

Say you load up a library like https://animate.style/ which offers a bunch of keyframes out of the box: fadeIn, bounceIn, …

If authors wants to chain these animations, they need to attach all of them and delay each subsequent animation. As demonstrated in this demo:

#target {
  animation: fadeInDown 2s, flip 0.5s 2s, fadeOutDown 2s 2.5s;
  animation-fill-mode: forwards;
}

This is a bit clunky, as the author needs to calculate each animation-delay by adding up all animation-duration values of all preceding animations.

Additionally authors cannot repeat this composed animation, as demonstrated in this demo: each individual animation is repeated, instead of the whole block.

#target {
  animation: fadeInDown 2s, flip 0.5s 2s, fadeOutDown 2s 2.5s;
  animation-fill-mode: forwards;
  animation-iteration-count: infinite;
}

(Sidenote: this iteration part could be solved by allowing a delay in between iteration – as proposed in https://github.com/w3c/csswg-drafts/issues/4459 – but this would be more number wrangling for the author)

--

It would be nice if authors had an easier way to do this. I was thinking of a way to compose the existing keyframes in a new keyframes block, one way or the other.

The goal would be to have a new set of keyframes that, for example:

  • run the existing fadeInDown from 0% to 45%
  • run the existing flip from 45% to 55%,
  • run the existing fadeOutDown from 55% to 100

This new set of keyframes could then be used as any other set of keyframes.

I was thinking somewhere along the lines of this:

@keyframes composed {
  0% 45% fadeInDown;
  45% 55% flip;
  55% 100% fadeOutDown;
}

Syntax of course to be discussed. Order of the arguments? Might we need another at-rule? Maybe extra blocks?

bramus avatar Aug 04 '22 19:08 bramus

I think this should be covered by group effects which are proposed for Web Animations level 2, however the corresponding CSS syntax has yet to be proposed.

I believe that would we preferable to composing CSS keyframes in this way because:

  • it makes the feature available and modifiable from JS
  • it can be extended to running effects in parallel, not just in sequence
  • it allows targeting multiple elements

That said, perhaps the ultimate shape of the corresponding CSS syntax might end up resembling this proposal in some cases.

birtles avatar Aug 08 '22 00:08 birtles

I always thought this would be covered by the animation-composition property, though that obviously defines the composition of different values for a single property.

Though looking at the group effects linked by @birtles. @bramus Your example could be simplified by writing this:

@keyframes composed {
  from fadeInDown;
  45% 55% flip;
  to fadeOutDown;
}

I.e. the first and last ranges are created automatically by the keywords or by defining 0% and 100%.

Sebastian

SebastianZ avatar Aug 08 '22 05:08 SebastianZ

Hmm, if one the positions (start / end) would be optional, I’d rather make the end one optional. Like so:

@keyframes composed {
  0% fadeInDown;
  45% flip;
  55% fadeOutDown;
}

Not too sure if any of these should be optional though, as the code above could be interpreted as “run fadeInDown from 0% to the end”.

bramus avatar Aug 08 '22 08:08 bramus

I think this should be covered by group effects which are proposed for Web Animations level 2, however the corresponding CSS syntax has yet to be proposed.

Oh nice, hadn’t seen that one before. The Sequence Effects indeed seem to be describing this :)

bramus avatar Aug 08 '22 08:08 bramus

Hmm, if one the positions (start / end) would be optional, I’d rather make the end one optional. Like so:

You're right. It makes more sense to get rid of the end position. Also, the to keyword and probably also the from keyword I suggested earlier would be misleading in this case.

Not too sure if any of these should be optional though, as the code above could be interpreted as “run fadeInDown from 0% to the end”.

That's a valid point. But making them optional makes the case of contiguous animations easier, which is very common. And it would allow authors to add and remove animations without having to care about their runtime ranges. They would be equally distributed similar to how gradient color stops work.

Just another thought: The fractional unit could also be used here. So this:

@keyframes composed {
  1fr fadeInDown;
  2fr flip;
  1fr fadeOutDown;
}

would be equal to this:

@keyframes composed {
  0% 25% fadeInDown;
  25% 75% flip;
  75% 100% fadeOutDown;
}

Sebastian

SebastianZ avatar Aug 08 '22 22:08 SebastianZ

I've yet to check out "group/sequence effects" but did start jamming on some solutions to this a while back where the custom properties would be generated for you to handle calculating the delays for sequencing things.

That "tricky" or "nice to have" part would be some form of animation-repeat-delay 🙏 Touched upon it in this CSS Tricks article. Anything to avoid padding keyframes 😅

Whichever solution is proposed to tackle this, I think it would be likely best to think in terms of how timelines are designed elsewhere. If you're composing something, it's likely the default thinking from the designer that things will chain. For example, take a look at how we would do things with Greensock's position parameter. You could be explicit about a start and end point or have things like the following. I'd played on the idea of a property. But, some way to say "run these keyframes in this way.

/* Basic chain */
animation-composition: fadeInDown, flip, fadeOutDown;
/* flip starts half a second before the end of fadeInDown. fadeOutDown runs at end of flip */
animation-composition: fadeInDown, flip <0.5, fadeOutDown;

Think this is where that notion of nested keyframes works quite well though because you'd be able to infinitely loop the timeline or compose it onto another depending on the performance limitations of doing so. And that kinda leans into how JavaScript animation libraries do this again. The issue I think would be getting the translation again. Working in fractions and percentages doesn't translate well to time. It would still rely on a developer to calculate the timings and then convert them to percentages/fractions of the composed keyframes 🤔

In something like GSAP, you'd define by time. But, then be able to speed up or slow that down by tweening the time of the timeline or managing the playback rate. Maybe something like.

/* keyframes name - keyframes duration - keyframes position [optional, defaults to end of last] */
@keyframes composed-timeline {
  fade 1s,
  /* Start at the same time as fade */
  flip 2s 0,
  /* Start 1s before the end of flip */
  fadeOutDown 1s <1s
}
/* Plays the composed timeline at a sped up rate */
animation: composed-timeline 1s infinite linear;

My random two cents whilst searching for something else on here 😅

jh3y avatar Aug 16 '22 08:08 jh3y

I've outlined my concerns with a repeat delay property here and here and I still believe group effects are going to cover more use cases in a more predictable and extensible manner without increasing the complexity of the model.

Most relevant to this issue is that groups extend well to other proposals such as time-based keyframe offsets and percentage-based durations/delays. That is, once you have groups, you have a context for resolving percentage-based times in, something that dovetails into the percentage-based times already proposed for scroll-driven timelines. (In effect, percentages on root effects get resolved against the scroll timelines, where as percentages on nested effects, get resolved according to their ancestors.)

birtles avatar Aug 16 '22 08:08 birtles