Closed dbaron closed 7 years ago
Firefox and Chrome have already shipped the frame() timing function. I'm sympathetic to the reasons for the change of name but I'm not keen on both those browsers having to maintain legacy aliasing support.
Yeah, we've also implemented it in Servo too. But to be fair, it hasn't reached release yet so we could change it without having to alias it, I think. I'll ask people on the animation slack to contribute here if they have any useful suggestions for names.
Hi from the slack.
When I came up with the name frames()
, one of the major use cases for this timing function was spritesheet animations, which were hacky or required modifying the spritesheet or animation in a specific way (I think Rachel Nabors was having tricky time trying to use the steps
function for this).
Root issue with the steps function is that when animating, the first & last step only appear in half the duration that the steps in the middle have, creating an inconsistent animation. This is especially noticeable with looping when passing the short last step and the short first step in succession.
If I remember correctly, older suggestions we had at the time were: equal
, distributed
, divided
and various other synonyms. Also variations with step(s) as suffix or prefix as an alternate form.
shshaw from slack suggested equal-step
None of these other names make much sense to the everyday frontend dev or designer. ('Steps' barely makes sense since people aren't graphing the timing function visually and will most likely never see it graphed!).
Frames made the most sense to me for my sprite animation use case, but let's consider an equally likely background color animation. Perhaps "jumps" or event "cuts" would make more sense. Let me ask the Twitters for inspiration.
Rachel's tweet, if anyone wants to re-tweet for wider reach:
CSS animations folks, if we could call steps() something else, what would you suggest?
She carefully didn't use the word frames
in her question. Yet 3 of 4 replies right now are recommending it.
I'm quite happy with frames()
but if others aren't and we assume steps()
is here to stay (and there are use cases for it) one other alternative might be steps-even
to indicate that the step values are evenly distributed between the start and end?
e.g. steps-even(5)
or perhaps steps(5, even)
(with the caveat that steps(1, end)
is valid but steps(1, even)
is not). frames()
and steps()
are only subtly different so having a similar name makes sense to me since when you discover that steps()
doesn't quite do what you want you'll naturally grab the next thing that sounds similar.
I like adding onto the steps()
syntax, personally. Use what is already there.
(Although, steps()
is still a horrible name for the timing function! Explaining it to students always elicits confusion. jumps()
may have made more sense, but alas. The dye is cast!)
In that light, "even" might not make sense to folks who may not even fully understand start
and end
.
What about steps(2, equal)
? I think "two equal steps" is a fairly easy-to-understand description of what this timing function achieves. It doesn't help clarify how its different from steps(2, start)
, though. Maybe steps(2, full)
?
But, my vote is still on keeping frames()
, especially since it has already shipped! That helps avoid confusion between the different ways of counting: steps()
counts the number of times the value changes, frames()
counts the number of different values.
The terminology could be reinforced with a new figure in the spec, showing the keyframes, the frames, and the steps as different parts of the timing function graph.
We had the "should we just reuse steps()?" discussion earlier, and decided not to do so, for precisely the reason Amelia gives - the two versions interpret the numeric argument substantially differently. Using a brand new name reduces the confusion factor.
I'd like to stick with frames()
here, as it invokes all the right intuitions - if you're animating a sprite sheet, it literally means frames, and even in the general animation case, instead of a smooth animation it's divided into N static frames.
While there is a little bit of semantic overlaap with rAF()
, I don't think the two are close enough to be worried about.
The other new piece of information that has just come up is people wanting to re-use timing functions for gradient easing. In that case frames()
seems less suitable? Should we care?
It seems okay to me?
Like, I agree that steps()
would have been better for this in the first place, but we done goofed that one up real bad.
Not sure if I follow, but we still need the steps()
behavior too. For example, when you rotate an object 1 turn infinitely and you want to stagger the progress (e.g. a segmented spinner), you don't want frames()
because you'll end up spending one portion of time at 0 degrees, and another portion of time at 360 degrees so you'll spending twice as long at that one position.
This is true!
The CSS Working Group just discussed reconsider name of frames() timing function
.
I don't strongly object to frames(), but it seems like a variant of steps()
, so maybe it should steps(n, <something>).
Maybe <something>
is a word that implies "inclusive"?
@smfr See the above comments (https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-300028913 in particular) for why re-using steps() is discouraged.
Given issue #1371, I'm now more inclined towards extending steps()
. I know we've had that conversation before, but since then two things have changed:
frames()
seems like a less suitable nameIn a sense, frames()
ensures both endpoints get their fair portion of showtime, while issue #1371 seeks the opposite effect: neither endpoint gets a showing.
These are really all variations on a theme so I feel like a common function name (or at least similar function names) makes sense.
Unfortunately steps(n, start)
means that the start value does not get a showing, otherwise I'd suggest the four keywords are: start, end, both, and none and they represent which endpoints are included (and we just accept the fact that the n
is interpreted differently for 'both' and 'none').
If we accept that the keyword represents where the vertical steps lie, then perhaps the keywords could be: start, end, inside, outside?
I'd have an easier time explaining that "both" is the equivalent of start AND end than inside and outside. But consider as well, "steps, none" seems to read that there are no steps happening at all!
Adding agenda+ since we need to resolve this issue soon. This feature has reached Firefox beta and will ship on the release channel in early August. I expect Chrome is not far behind. Beyond that point it will be much harder to change this naming.
To summarize the issue:
frames()
since:
steps()
, namely values of n
less than 2 are invalid.requestAnimationFrame
) and keyframes (@keyframes
etc.).frames()
is a less suitable name (#1332).Since I will not be able to attend telcon this week (I can only attend the APAC-friendly telcons), I'll leave my take on the issue here:
steps()
somehow.
frames()
and possibly yet another timing function which are only subtly different to steps()
but have completely different names feels like a unnecessary platform wart that will make learning this syntax harder for authors (when you realize that steps(2, start)
doesn't do what you want, it's much more natural to reach for something that looks similar) and that we will regret later.frames()
in a gradient also seems odd.start
and end
refer to where the step happens and not which endpoints are included. So in keeping with that precedent:steps(n, start)
— n ≥ 1steps(n, end)
— n ≥ 1steps(n, inside)
— n ≥ 2 (= frames(n)
, i.e. the steps are inside the interval)
inclusive
for this.steps(n, outside)
— n ≥ 1 (neither endpoint is included)
exclusive
(see next comment).start
and end
and try something that might be less mathematically consistent but is more intuitive.steps(n, start)
— n ≥ 1steps(n, end)
— n ≥ 1steps(n, distribute)
— n ≥ 2 (= frames(n)
, i.e. "distribute the steps evenly within the interval")space
(space apart)? equal
(equally distributed)?steps(n, clip)
— n ≥ 1 (neither endpoint is included, i.e. clip off the endpoints)trim
(trim the gap between the steps and interval ends)? align
(align steps with ends of interval)n
is different for the frames()
case but I'd still prefer a name that is obviously related to steps()
. Also there is a subtle difference in how frames()
works at the at boundaries that might suggest a separate function name.steps-inside(n)
with n ≥ 2, steps-outside(n)
with n ≥ 1steps-inclusive(n)
with n ≥ 2, steps-exclusive(n)
with n ≥ 1steps-distribute(n)
with n ≥ 2, steps-clip(n)
with n ≥ 1Maybe inclusive
and exclusive
instead of inside
and outside
? They are more academic terms, but are how you would actually describe a range that may or may not include the endpoints.
E.g., for a transition from 0 to 10, steps(5, inclusive)
divides the transition into five equal-sized steps between 0 and 10, inclusive. While steps(5, exclusive)
divides the transition into five equal-sized steps, exclusive of the initial and final values.
It doesn't help solve the naming confusion with start
and end
& people thinking that means "including the start/end value" instead of "making the switch at the start/end of the time interval". But I don't think it makes it much worse, either.
PS, Whatever you call it, the outside/exclusive/clip option should only be restricted to n ≥ 1. I would expect that steps(1, exclusive) for a transition from 0 to 10 would mean that the value would be 5 for the length of the transition.
PS, Whatever you call it, the outside/exclusive/clip option should only be restricted to n ≥ 1. I would expect that steps(1, exclusive) for a transition from 0 to 10 would mean that the value would be 5 for the length of the transition.
Oh, good point. I'll update my previous message to reflect that.
I'm theoretically okay with changing the name away from frames()
, to make something that works better with #1371, but I'm still strongly against reusing steps()
. The behavior is substantially different, and if you try align your mental model of the behaviors, then the interpretation of the numbers and keywords vary in confusing ways. (The aforementioned confusion with "start" not meaning "include the start value".)
I feel like the most intuitive model, for me, is something that creates N steps, and has a keyword that controls each combination of whether the start/end values are included. Keeping the "frames" name for a moment just for the sake of argument, I'm thinking of something like:
frames( <integer>, [ drop-start | drop-end | drop-both ]? )
(Alternately, [ drop-start || drop-end ]?
)
We might be able to merge this into steps()
, if we (a) come up with a good keyword for "keep both", and (b) are okay with steps(n)
continuing to mean drop-end
for legacy reasons. Using a new function lets us start with "keep both" as the default, so we don't need to come up with a new keyword. I don't have a good name for this tho; I'm still generally okay with frames()
for gradients too. ^_^
I'm opposed to inclusive
and exclusive
, btw - I think the words are above our desired complexity level, and even I, a CS/Math college grad, regularly have to pause for a moment to remember what each means whenever I use them. (This is why I suggested the drop-*
pattern - it's simple and easy to understand what it does.)
Sorry for the multiposts: a big reason I'm opposed to steps()
as a name is exemplified perfectly by the final paragraph of https://github.com/w3c/csswg-drafts/issues/1371#issue-228525559:
From a programming perspective all the graph examples should have a N value of 4 and a option describing the range, e.g. from top left: steps(4, openclosed), steps(4, closedopen), steps(4, open), steps(4, closed), but I know that would make no sense to a lot of designers.
The logic here is that all of them have four "steps", in the sense of distinct values that show up in the graph. This understanding is encouraged by the name. But it doesn't match user expectation in any way - the animation has 2, 3, or 4 visible values expressed during the animation's duration, and so should be expressed with an argument of 2
, 3
, or 4
. A name that is instead focused on the values themselves is a little better - I think frames()
does this reasonably well, and would want something with a similar framing if we don't use frames()
itself.
Thanks for following up on this. I'm afraid that drop-*
sounds like one or more of the endpoints are dropped, but they're not--it just appears that way when you use a fill mode of none
.
I suspect I'm forgetting something really obvious here but I wonder if maybe we should stop trying to be clever by having frames(n)
where n
is the number of frames. Maybe we should just let n
continue to be the number of steps (i.e. changes in value) and add the distribute
and justify
keywords to describe how the steps are aligned within the interval (much like start
and end
do).
If we do that frames(2)
would become steps(1, distribute)
. Likewise, frames(4)
would be steps(3, distribute)
.
steps(1, justify)
probably equals steps(1, distribute)
.
Maybe I'm just being dyslexic, but I'm having trouble matching up the keywords with the graphs.
Do we have a list of updated graphics that we can put the new names under them to get a feel of this?
I'd also agree with Brians' last email regarding keeping it in name to the steps as the function name implies to avoid the dual context issue.
I think documentation and a little explanatory tool would help make it easy for people to digest and make the connection in their heads with. (My favourite is the self-aware border radius tool :) https://s.codepen.io/zadvorsky/debug/mJQWzQ )
On Thu, 22 Jun 2017 at 06:17 Brian Birtles notifications@github.com wrote:
I suspect I'm forgetting something really obvious here but I wonder if maybe we should stop trying to be clever by having frames(n) where n is the number of frames. Maybe we should just let n continue to be the number of steps (i.e. changes in value) and add the distribute and justify keywords to describe how the steps are aligned within the interval (much like start and end do).
If we do that frames(2) would become steps(1, distribute). Likewise, frames(4) would be steps(3, distribute).
steps(1, justify) probably equals steps(1, distribute).
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-310277916, or mute the thread https://github.com/notifications/unsubscribe-auth/AHPBA9SJgohoPdX-_06QKxB2YwSuxeVMks5sGfjwgaJpZM4NJwYI .
Here we go (sorry, it's not self aware but hopefully it helps the discussion):
Thanks for the drawings @birtles !
I think using a steps(3)
function to create four frames (or two frames) is very confusing, especially if you're using it for flipping between actual animation still frames in a sprite sheet!
Whatever the complaints about the start/end nomenclature, at least it's always clear that you get as many values as you specify, and that the time is divided into that many equal intervals.
I'm still in favor of frames(n)
for n equal intervals, including both end points. The n still has the same meaning from the perspective of dividing up the time into n intervals. But the different name shows that we are counting/calculating the values differently.
Has there been any real demand for dropping both end points, or can that be deferred to a future spec?
The trouble is you generally don't get as many values as you specify with the start/end nomenclature. steps(3, end)
gives you four values, and so does steps(3, start)
(either in-effect or based on the before flag behavior).
In the typical case where you have a transition (which effectively fills forwards and backwards) or any animation that does not repeat (in which case you normally want to fill, or effectively fill by relying on the non-animated value either side of the animation interval) then when you watch the animation, for each of the examples in the diagram above you see four values (i.e. three jumps).
The most common case where you don't see all four values is when you have a repeating animation and are watching one of the intermediate iterations. Maybe someone could argue that frames()
is especially useful for repeating animations, but I don't think that's the case. It's quite useful for single iteration animations too, and, conversely, start/end is very useful for repeating animations whenever the endpoint and startpoint of the animation overlap since with frames()
you'd end up with one frame showing for twice the time. That is, these feel like the same sort of thing to me so having a completely different name and way of counting seems unfortunate.
I guess it comes down to whether you want to count the number of values or jumps. If we were starting from scratch I think we agree we'd probably want to count the number of values, but since we've got steps()
which counts the number of jumps (i.e. values - 1
) I think we should stick with it. I introduced frames()
at a developer meetup here in Tokyo after the last CSS F2F and it certainly felt awkward explaining why I had to change the code from steps(10, start)
to frames(11)
despite the visual result being so similar.
Dropping both end points has so far only come up in the context of gradients. It could definitely be deferred but I think it's relevant when considering how to name this class of things. I'd rather not introduce yet another function for that too.
In the typical case where you have a transition (which effectively fills forwards and backwards) or any animation that does not repeat (in which case you normally want to fill, or effectively fill by relying on the non-animated value either side of the animation interval) then when you watch the animation, for each of the examples in the diagram above you see four values (i.e. three jumps).
You're getting lost in the simple mathematics of the system, and are forgetting the bad usability that originally prompted Rachel's request for frames()
. There are at least two easy cases where steps() is very bad:
In both of these, you need to use N+1 steps when you intend to only show N "frames", and overshoot your goal value by 1/(N+1). (With frames(N)
you get the right behavior automatically.)
Another bad situation is when you're transitioning, and you want the transition to start immediately, show N intermediate steps, and end T seconds later. To do this with steps()
, you need to use start
, and increase the duration by 1/(N+1). (This wants the "exclude both start and end" thing - it's more than just gradients!)
All of these scenarios count as being "possible", but they're terrible in practice.
steps()
is well-optimized for repeating animations where you want to end back where you started. (This makes sense, as the entire design of CSS animations is heavily optimized toward infinitely-looping animations.) Then start
vs end
is just a matter of whether the animation begins with its starting value or the first "step". In a lot of other cases, steps()
is very bad, because it focuses on jumps rather than values, which makes it match up badly with the mental models people are trying to express.
Thanks for following up on this. I'm afraid that drop-* sounds like one or more of the endpoints are dropped, but they're not--it just appears that way when you use a fill mode of none.
Other names that better communicate "don't use the start/end value in the animation" are welcome, of course. ^_^ Or, ultimately, we can leave steps()
to doing the "unbalanced" thing, and keep frames()
focused on just the one scenario. (Possibly, if we figure out a good word, letting it do the "just interior frames" thing too.)
Maybe we should just let n continue to be the number of steps (i.e. changes in value) and add the distribute and justify keywords to describe how the steps are aligned within the interval (much like start and end do).
I am so extremely against this. It's the ultimate in prioritizing the math over the UX, which is the entire problem here. For all that steps(N)
is bad, at least we have the nice, intuitive property that it divides the animation into N intervals. (The intervals just don't have the values we want in many cases. ^_^) Having a keyword make it N-1 or N+1 intervals would be extremely confusing I think.
There are at least two easy cases where steps() is very bad:
- A one-shot animation not intended to fill.
- A repeating animation that doesn't end where it started.
I agree that these are the two cases where the frames()
notation makes most sense. I'm just not sure if we should prioritize these two situations to the point of breaking consistency with the existing set of functions. Perhaps if they represented the overwhelming majority of animations that prioritization would make sense, but I'm not sure they do. For a start, they can never occur with transitions.
It's the ultimate in prioritizing the math over the UX
That's not a fair characterization. On the contrary, this proposal is entirely motivated by UX. That is, I've stood in front of developers and tried to explain why these two effects that are nearly identical have completely different names and different numbers and it was awkward. I'd rather say, "You know how this steps()
thing doesn't do what you want in this particular situation so you're adding extra keyframes and delays and so on--well, now you can just change the keyword at the end and you're done." Besides, I'm really bad at math so I'm hardly going to prioritize it!
Anyway, I'm not going to push this anymore. I just felt that the argument for extending steps()
needed to be made. I still think it will be unfortunate to stick with frames()
but if no one else feels likewise I guess this will just ride the trains and join the rest of the interesting features in the platform and I'll refine my developer pitch.
I still think it will be unfortunate to stick with
frames()
but if no one else feels likewise ...
I do!
'Steps' barely makes sense since people aren't graphing the timing function visually and will most likely never see it graphed!
Graphing the timing functions is not the point here. It's that steps()
simply sounds logical, because the transition makes several steps until it reaches its end value. In that sense jumps()
would as well be ok.
frames()
also sounds fine for me in the context of timing functions. Though the earlier mentioned use case of reusing the timing functions in the context of color gradients (and by that making them "transition functions") is the real conflict here, because "frames" doesn't make much sense in a visual context.
Therefore I agree with Brian, that the function should be renamed or its functionality be integrated into steps()
.
But, my vote is still on keeping
frames()
, especially since it has already shipped!
While it already got implemented in Blink and Gecko, as far as I can see, those implementations didn't reach stable releases yet, so it could still be changed.
Sebastian
The CSS Working Group just discussed reconsider name of frames() timing function
.
It is not a bad thing to have more than one way of expressing something. Colors have several different ways of being declared.
I like this approach:
frames( < integer >, [ drop-start | drop-end | drop-both ]? )
But maybe something closer to flex terminology since distribute is visually similar justify-content: stretch:
frames( <integer>, [stretch | start | end | center]? )
I like the idea of leaving steps() intact and moving functionality forward with frames.
I think you want the space distribution keywords: space-around
, space-between
, space-evenly
. Or alternately replace "space" with something else (maybe "pause" or "fill"). But certainly we should follow this existing pattern as it's both clear and consistent.
Imho, the following is pretty clear:
steps( [ <integer> || [ fill-start | fill-end | fill-evenly | fill-between | fill-around ]? ] )
The number of steps is just as if your graph were the floor: there is one step if there is one jump, and there are two surfaces, one lower, one higher. How the steps are distributed in time (or space, if we re-use these functions for gradients) is using the keywords we are familiar with from Grid/Flexbox/Align. The addition of fill-start and fill-end allows for the asymmetric options: fill-start fills before the first step as it does between them, and fill-end fills after the last step as it does between them.
I understand the desire for frames(), but I don't think having a completely unrelated name for a function that is almost exactly the same as one that exists, but slightly different, is particularly helpful. (It'd be like adding :nth-index() so we could have zero-indexed :nth-child().) We've already chosen to count the steps rather than the frames, seems we should just continue on that path. I think the behavior expressed is particularly clear by maintaining the analogy to physical steps, fwiw.
I did try to re-use the spacing keywords from flexbox but it seemed like a few keywords would produce the same result given that the steps are of equal length and I couldn't decide which one to use. It also felt like while the keywords make sense when thinking about a graph, they were a little less intuitive in the context of imagining an animation moving (which is how we expect most people to be thinking). I agree though, if we can make them work, then that seems attractive.
Do fill-start and fill-end overlap with animation-fill-mode
? If so I think that would be a layering violation (whether an animation applies before/after the animation interval should be independent of how it progresses within that interval).
Do fill-start and fill-end overlap with animation-fill-mode? If so I think that would be a layering violation (whether an animation applies before/after the animation interval should be independent of how it progresses within that interval).
No, I was suggesting them as keywords for the first two diagrams in https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-310571203 -- specifically, fill-end would be the first one and fill-start the second (as "fill" is indicating where the spacing is going in this scheme).
I'm strongly opposed to trying to "harmonize" this with the content-distribution keywords. It's extremely non-intuitive in practice, imo, particularly since it means you're distributing the jumps, not the values; the entire point of frames() is that it's more intuitive in some cases to think of the values. (And steps() can be interpreted as N specifying the number of jumps or values, both in its name and its operation)
Basically I don't think talking about jumps is ever the intuitive thing to do. At least in my mind, it's never what I'm considering when dealing with transitions, except possibly in the simplest cases (like, I might think "I want this to transition in one jump", and then decide whether I want the jump to happen at the start, middle, or end; but as soon as I'm talking about more than that, I want to think in terms of how many values I see in the animation).
It's extremely non-intuitive in practice, imo, particularly since it means you're distributing the jumps, not the values
That sounds like renaming it to values()
would help. Or restrict the keywords to the ones that don't cause this mental conflict.
Sebastian
My 2 cents:
Indeed, frames()
is much more intuitive than steps()
, both in terms of naming and functionality. I don’t see any issue with collisions with requestAnimationFrame
or @keyframes
, by that logic requestAnimationFrame
collides with the <frame>
tag!
However, we can't rename steps()
, and having two very similar functions seems worse from a usability pov. So I would vote for extending the second parameter of steps()
to cover the current definition of frames()
. No strong opinions about the name of that parameter, but justify
made the most sense to me. It's unfortunate we cannot make this the default. 😢
frames()
is also a very animation-centric name, which could prove a problem down the road. In fact, I've suggested that the entire spec needs to be renamed and reworded to be more about generic interpolation and less animation-specific.
That sounds like renaming it to values() would help.
I can get behind that! And it works well for gradients as well, avoiding @LeaVerou's concern.
values()
feels like it causes even more namespace concerns than frames()
to me. Can we get any more obscure? How do I explain values(5)
to a student? "Instead of a steady distribution of change, it creates 5 equally spaced values..." Hrm. Can someone phrase it better?
Yeah, that's precisely how I'd phrase it. What's wrong with it?
Well, remember many spec folks didn't have a problem with steps()
to begin with ;) What sounds right in our ears might not sound right to folks working with CSS every day or getting into it from a non-spec-oriented space. Thinking back to my younger days, the concept of "values" of properties was a bit nebulous for me, in terms of change over time. And trying to think about how that fits with cubic-bezier curves and complex timing functions explanations...
I'll ask the folks on the Slack how they feel about it, regardless :)
My two cents: values
is a very vague/ambiguous term. Which, I understand the appeal for using it outside of just animations, but still, in isolation (without context) I have no idea what its purpose/goal is.
Also, values
does not convey the notion of "evenly distributed" - values, by the nature of the word, can be heterogeneous.
Some other bikeshedding ideas:
partitions
fragments
segments
Option for keeping steps()
while being more explicit about what is happening:
steps( <integer>, [ trim-start | trim-end | trim-both | no-trim ]
or
steps( <integer>, [ drop-first | drop-last | drop-ends | drop-none ]
Riffing on an earlier comment by Tab :)
As I mentioned before, I'm not keen on the drop-*
terminology because most of the time you do actually see those endpoints either in the fill or the effective fill. As an example, consider the visual result if you apply those timing functions to a transition.
That makes sense. I'm at a loss for a way to explain that intuitively, though!
A continuation of @davidkpiano's suggestion of names, but parts(n)
is generic, but might provide more context than values(). It is not animation specific, and I think it is self descriptive.
From https://github.com/w3ctag/design-reviews/issues/161#issuecomment-297617664, it would be good to reconsider the name of the
frames()
timing function.