Open vrugtehagel opened 3 years ago
One thing to consider is that at the moment the order of the keyframe rules doesn't matter. When a keyword like via
is introduced, their order gets relevant.
Also, the keyframe selector takes a comma-separated list, so that also applies in that case. So the following case
@keyframes x {
from { color: blue; }
via, via, 30%, via { color: yellow; }
to { color: lime; }
}
would then be equivalent to
@keyframes x {
0% { color: blue; }
10% { color: yellow; }
20% { color: yellow; }
30% { color: yellow; }
65% { color: yellow; }
100% { color: lime; }
}
Sebastian
I must admit, I did not consider the order as I rarely ever make use of this feature, but in the case that the "absolute" keyframes selectors aren't ordered properly, there are two things we could logically do.
via
keyframe selector is only valid when its lower bounding absolute keyframe is less than its upper bounding absolute keyframe (where "lower" and "upper" refer to their position, not their value), orvia
keyframe selector always interpolates between its nearest bounding absolute keyframe selectors, regardless of their value.So let's say we have
@keyframes blink {
90% { opacity: 1; }
via { opacity: 0; }
0% { opacity: 1; }
}
Then the first rule would say "this via
keyframe selector is invalid", dropping that keyframe block. The second would instead say "this is fine" and take the via
to mean 45%
. I'm not sure which of these is preferable to the general public, but I'd prefer the second for flexibility and consistency. All in all the order of the keyframes doesn't seem too much of an issue.
We need to calculate the new keyframe selectors and approximate decimal values (e.g. the 66.667% in the above example)
Another possible solution could be the ability to specify the ending percentage, defaulting to 100%. So you would write
@keyframes rainbowText {
@end 4%;
0% { color: red; }
1% { color: orange; }
2% { color: yellow; }
3% { color: green; }
4% { color: blue; }
}
Then remove orange
@keyframes rainbowText {
@end 3%;
0% { color: red; }
1% { color: yellow; }
2% { color: green; }
3% { color: blue; }
}
@Loirooriol while that does solve the decimals, it does not address the issue that you need to edit multiple keyframe selectors upon adding or removing one (e.g. if I want to remove 1% { color: orange; }
in your first example, I still need to edit the 2%
, 3%
and 4%
, as well as the defined @end
). It also feels a bit odd since percentages by definition work on a scale from 0-100, and this modifies that scale so they technically wouldn't be percentages anymore.
I agree with @vrugtehagel about your proposal, @Loirooriol. Instead of bending the meaning of the percentages that way, integers would make much more sense in this case. Then the keyframes would be sorted by them, ascending. By that I mean that the integer values would only define the order of the keyframes but not have any meaning on where exactly the keyframe is positioned, i.e. they would be evenly spread across 0% and 100%.
So, instead of
@keyframes rainbowText {
@end 4%;
0% { color: red; }
1% { color: orange; }
2% { color: yellow; }
3% { color: green; }
4% { color: blue; }
}
you'd write something like
@keyframes rainbowText {
0 { color: red; }
10 { color: orange; }
20 { color: yellow; }
30 { color: green; }
40 { color: blue; }
}
or
@keyframes rainbowText {
0 { color: red; }
5 { color: orange; }
10 { color: yellow; }
15 { color: green; }
20 { color: blue; }
}
Both of them would be equivalent to
@keyframes rainbowText {
0% { color: red; }
25% { color: orange; }
50% { color: yellow; }
75% { color: green; }
100% { color: blue; }
}
Then removing one keyframe or adding one in between would be less of a problem. Though one disadvantage would be that you don't have any influence on the exact positioning of the keyframes as they are evenly spread. And if you added more keyframes in between two keyframes, you'd still have to adjust one of them. Furthermore, @vrugtehagel's suggested syntax is less disruptive and integrates better with the exising @keyframes
syntax.
@vrugtehagel Note that an @keyframes
rule could also look like this:
@keyframes x {
90% { opacity: 1; }
via { opacity: 0; }
0% { opacity: 1; }
50% { opacity: 0.5; }
}
(Not saying, anybody should actually write a rule in such a chaotic order. 😄)
So, a third solution for the order issue would be to make the whole @keyframes
rule invalid when the order is mixed and includes one or more via
keyframes. Having said that, I probably wouldn't prefer that solution but rather one that handles this situation gracefully. Though I don't have a strong opinion for one of the two you mentioned. I just wanted to point out that this case needs to be handled. And there might also be other ways to handle them.
Another case that needs to be specified is what happens when a via
keyframe is placed at the beginning or the end of an @keyframes
rule.
Possible solutions I see here are
@keyframes
rule invalid. (Again, not my favorite, rather mentioning it for completeness.)via
resolve to 0% at the beginning and 100% at the end of the @keyframes
rule.via
keyframe rules interpolate between 0% and the first percentage keyframe.via
keyframe rule invalid.In this case, I'd prefer option 3, as it is the nearest to what was suggested.
Sebastian
I indeed agree with option 3 there. We can already leave out the start and end of a keyframe animation; the specs clearly state
If a 0% or from keyframe is not specified, then the user agent constructs a 0% keyframe using the computed values of the properties being animated. If a 100% or to keyframe is not specified, then the user agent constructs a 100% keyframe using the computed values of the properties being animated.`
I would not like to change this; via
should not be able to "replace" from
and to
. Writing a keyframe animation like so:
@keyframes hop {
via { transform: translateY(-10px); }
}
would mean it resolves to 50%, and, as the specs currently specify, 0% and 100% will be constructed using the computed values.
I think the biggest ambiguity we have is what happens when, for example, someone writes
@keyframes fadeOutIn {
0%, 90% { opacity: 1; }
via { opacity: 0; }
50% { opacity: 0.5; }
}
Even without the via
keyword, this is hard to read, but I admit the via
keyword adds some complexity as I now have to think about what it represents. Note that, while writing this is possible, it should be encouraged that authors write their keyframe selectors in order when using via
to increase readability. Either way, the above would logically be equivalent to
@keyframes fadeOutIn {
0% { opacity: 1; }
50% { opacity: 0.5; }
70% { opacity: 0; }
90% { opacity: 1; }
}
That is, even when using keyframe selector lists (such as 0%, 90%
) the order will be relevant to how via
will behave (should it follow, be included in, or precede such list).
Can anyone think of a case where you would actually want to build your @keyframes
out of order? It seems odd to me that, we would need to support that. I'm sure some people are doing it because it's been allowed in the past, but it seems like a very strange practice to me. The animation has to execute in a specific order so why not expect the definition/input in the same order?
I would suggest having a look at how GSAP and AnimJs handle keyframes. These are just a few examples. Having used many different animation libraries over the last 20 years, I'd really love to see CSS come closer to their syntax. It's way more flexible for defining where you want different easing types and durations to occur, how long your overall animation should run and for interrupting animations and changing the motion paths to a new animation midway through.
I don't want to blow up the scope of this issue. I think the via
keyword is a great idea, but it leads me to think that the current %
-based syntax is less than ideal. Perhaps we need a completely separate @sequential-keyframes
syntax?
Yes, the most obvious case is something like 0%, 100% {...} 50% {...}
.
Writing your keyframes out of order can be useful. Specifically, people tend to do this whenever blocks to different keyframes are the same; it allows you to entirely omit one of the blocks. Tabatkins shows a simple example, but it extends to bigger animations as well. This is a good point, though; if you have big animations, shortened by grouping some of the keyframes into lists of keyframes, then via
becomes somewhat useless. You'd have to choose to either write them in order, and repeat the blocks, or not use via
at all.
However, I still think via
would be helpful. The above case is, as far as I know, pretty rare. Markhicken's doubt that anyone writes keyframes out of order would further support that. However, I decided to write an HTTP archive query to further back this statement.
Turns out, about 89.96% of all keyframe definitions are defined in the proper order. Tabatkins' example is, funnily enough, only the second most common case with 0.8%. The most common (at 1.3%) non-ordered animation is this:
@keyframes mostCommonWeirdlyOrderedAnimation {
0%, 60%, 75%, 90%, 100% { ... }
0% { ... }
60% { ... }
75% { ... }
90% { ... }
100% { ... }
}
which looks like it could be bounceInDown
from animate.css (though that's just a guess). These keyframe selectors are technically not in order (as there's a 0%
following a 100%
), but via
would still be useful in cases like these.
Either way, just by the fact that only 10% of the keyframe animations are not in the correct order, I'd say via
would still be a good addition to CSS. Even amongst the 10% there are still cases like the above case where via
could be useful.
As far as a separate rule like @sequential-keyframes
goes - it would indeed be blowing this out of scope a little. This proposal is intended to make the use of @keyframes
more fluent, not to extend its possibilities. via
is handy even for smaller, more straightforward animations. If you have ideas for a non-percentage based keyframe syntax, that should probably be its own proposal. Even then, it probably won't completely replace @keyframes
so a little boost to @keyframes
' usability will still make CSS better.
YES let's please address the use case! As an author I have very frequently been frustrated with this.
Out of the ideas proposed above, the one with <number>
made the most sense to me.
I’ll add another idea to the pile, possibly bad but it may help brainstorming.
What if we could invert the grouping of keyframes and properties, and we could just list the values a given property will go through and have the UA automatically figure out the keyframes, akin to how gradient color stops work?
I.e.
@keyframes rainbowText {
color {
values: red; orange; yellow; green; blue;
}
}
Note that this would allow us to do things like:
@keyframes rainbowText {
color {
values: red; orange; yellow; green; blue;
}
opacity {
values: 0; 1; .5;
}
}
without all the annoying math this would require to do with keyframe percentages.
What if we could invert the grouping of keyframes and properties, and we could just list the values a given property will go through and have the UA automatically figure out the keyframes, akin to how gradient color stops work?
The Web Animations API allows this. That is, you can provide a list of keyframes or a list of values per property. See the examples at: https://drafts.csswg.org/web-animations-1/#processing-a-keyframes-argument
It also allows omitting the keyframe offsets in either case. Whatever we do here should probably align with that to some extent.
@LeaVerou that's a great idea! The syntax looks a bit weird to me, but I suppose we can't really accomplish that idea without new syntax. I thought first that separating the values with /
rather than ;
would be nice, but that wouldn't actually work for properties that already use it (e.g. animating border-radius
that has values containing slashes). Then I thought a function could work, like opacity: animate(0, .7);
but that causes issues when you're animating values that already use commas.
@birtles makes a good point as well. We can define a Keyframe
object in JavaScript in such a way already, and so it would be nice to align CSS with this. This proposal, as birtles observed, is a step in that direction, though just does not deal with the "inverted" way of writing keyframes. Specifically, via
addresses the following JavaScript Keyframe
feature (from MDN's page on Keyframe formats):
It is not necessary to specify an offset for every keyframe. Keyframes without a specified offset will be evenly spaced between adjacent keyframes.
The only difference here is that in JavaScript, your keyframes must be in the right order whereas CSS allows you to define them in any order you want.
Anyway, while I do really like the concept of being able to "invert" the keyframe definition as LeaVerou suggested, I would again lean towards that being a separate proposal. That and this proposal are similar and obviously related but are simply different features, and solve slightly different use cases. Let me illustrate this with an example:
@keyframes wiggleThenRainbow {
0% { left: 0; }
10% { left: -10px; }
20% { left: 10px; }
30% { left: -10px; }
40% { left: 0; color: black; }
55% { color: red; }
70% { color: blue; }
85% { color: yellow; }
100% { color: green; }
}
This animation is hard to write with inverted notation (if even possible at all) but would benefit from via
. In particular, 10%
, 20%
30%
, 55%
, 70%
and 85%
could be replaced by via
. Then, you could just change the 40%
to adjust where the wiggling ends and where the rainbow starts. It also allows you to much more easily add or remove a wiggle or rainbow color.
However the inverted syntax would be very useful for cases like this:
@keyframes wiggleAndRainbow {
0% { left: 0; color: black; }
20% { color: red; }
25% { left: -10px; }
40% { color: blue; }
50% { left: 10px; }
60% { color: yellow; }
75% { left: -10px; }
80% { color: green; }
100% { left: 0; color: black; }
}
You could, in this case, use via
if you wanted, and write
@keyframes wiggleAndRainbow {
from {color: black; }
via { color: red; }
via { color: blue; }
via { color: yellow; }
via { color: green; }
to { color: black; }
from { left: 0; }
via { left: -10px; }
via { left: 10px; }
via { left: -10px; }
to { left: 0; }
}
That would, similar to the previous example, simplify adding or removing a wiggle or rainbow color, though the inverted syntax would most definitely make more sense than via
here. Either way, these two examples demonstrate the difference in use case for via
and the inverted syntax.
Anyway, while I do really like the concept of being able to "invert" the keyframe definition as LeaVerou suggested, I would again lean towards that being a separate proposal.
I agree with that.
For what it's worth, a keyword could also be used for that syntax instead of the semicolon, e.g.
@keyframes rainbowText {
color {
values: red to orange to yellow to green to blue;
}
opacity {
values: 0 to 1 to .5;
}
}
That's probably not as readable as using an existing separator in some cases but wouldn't conflict with existing syntax.
And it needs a keyword that is currently not used in any syntax. to
is just an example and is already used, I think.
@keyframes wiggleAndRainbow { from {color: black; } via { color: red; } via { color: blue; } via { color: yellow; } via { color: green; } to { color: black; } from { left: 0; } via { left: -10px; } via { left: 10px; } via { left: -10px; } to { left: 0; } }
Having multiple animations within one @keyframes
rule is also out of scope for this proposal, I'd say. Also, it isn't needed. The current logic allows to combine multiple animations using the animation
property. So you would split both animations into separate rules like this:
> @keyframes rainbow {
> from {color: black; }
> via { color: red; }
> via { color: blue; }
> via { color: yellow; }
> via { color: green; }
> to { color: black; }
> }
>
> @keyframes wiggle {
> from { left: 0; }
> via { left: -10px; }
> via { left: 10px; }
> via { left: -10px; }
> to { left: 0; }
> }
And then animate them individually within animation
. Furthermore, with animation-composition
they can be combined the way you suggest in your example with the percentages.
Sebastian
@SebastianZ the last example I gave, wiggleAndRainbow
, is, apart from the via
keyword, already possible. It's not multiple animations within one @keyframes
, it's still just one animation, the properties are just split (in an admittedly weird way). Either way, it was just an example to demonstrate that you can still utilize via
even if you have more than one property that you want to linearly interpolate between. In this case, you can indeed just split them, but you may not always want to; for example, if you will only ever use them together and want to only specify one animation in the animation
property.
We recently discussed which separators we can use for arbitrary values, as it was needed for mix()
and last-supported()
, and concluded that ;
was the only one that is guaranteed to not be used by any property value.
Proposal: the
via
keyframe selectorProblem
When we write CSS keyframe animations, especially large ones, it is not uncommon that we need to add or remove a keyframe. In a lot of cases, the keyframe selectors are evenly-spaced, so adding a keyframe requires us to edit every keyframe selector in that animation. For example, say we wrote:
Now, we test it and see the orange is a bit ugly, and we want to remove it. We rewrite the above to
We now notice it's not very rainbow-ey, so want to try to re-add orange, but add purple too this time... We rewrite all the keyframes, etcetera, you get the point.
There's two issues here:
66.667%
in the above example)Proposal
Here's my solution to this: the
via
keyframe selector. Essentially, it would linearly interpolate between the closest bounding absolutely specified keyframe selectors. Let me demonstrate by example.Example usage
The original
rainbowText
animation would end up looking likeNow, adding a keyframe is a breeze, because we don't need to worry about the keyframe selectors for the other keyframes. A little more of a complex example, showing how you can mix
via
and absolute keyframe selectors:The
via
keywords interpolate between the bounding absolute keyframe selectors (25%
and50%
), so it would be equivalent toThis would also allow for keyframe selectors like (I'll list them out here rather than making up a long animation to demonstrate)
[from, 10%, via, via, 40%, 52%, 70%, via, 80%, to]
, which would be equivalent to[0%, 10%, 20%, 30%, 40%, 52%, 70%, 75%, 80%, 100%]
.Links
Original discourse post (also by me): https://discourse.wicg.io/t/proposal-css-keyframes-via-keyword/5219 Relevant part of the spec: https://drafts.csswg.org/css-animations/#typedef-keyframe-selector