Csswg-drafts: [css-timing] reconsider name of frames() timing function

Created on 27 Apr 2017  ·  60Comments  ·  Source: w3c/csswg-drafts

From https://github.com/w3ctag/design-reviews/issues/161#issuecomment-297617664, it would be good to reconsider the name of the frames() timing function.

css-easing-1

Most helpful comment

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.

All 60 comments

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.

The full IRC log of that discussion
<dael> Topic: reconsider name of frames() timing function

<dael> Github topic: https://github.com/w3c/csswg-drafts/issues/1301

<Bert> (Konqueror 4.14 (current) and Opera 12 (old) treat the test case as per the spec. How much content is still out there thatr was made wih Mac OS 9?)

<dael> Rossen: Long issue, added by dbaron. Has some ML discussion.

<dael> TabAtkins: I feel like github is having fruitful discussion

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:

  • There is interest in re-using this for gradient stops where frames() seems like a less suitable name
  • There is interest in a fourth variation on this theme where neither of the endpoints are included

In 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:

  • We initially decided to use frames() since:

    • It has slightly different behavior to steps(), namely values of n less than 2 are invalid.

    • There was strong community support for this name (the proposal itself came from Martin Pitt via the Animation at Work slack). That this naming makes sense to many people has been been subsequently confirmed by Rachel's informal twitter poll.

  • Since then the TAG raised concern about semantic overlap with animation frames (requestAnimationFrame) and keyframes (@keyframes etc.).
  • Subsequent to that interest has been expressed in using timing functions for gradients where frames() is a less suitable name (#1332).
  • Furthermore, there appears to be interest in coining a fourth variation on step timing functions where neither of the endpoints are included (#1371).

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:

  • Given the last two points in particular, I think we should rename this by simply extending steps() somehow.

    • Having 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.

    • Sticking frames() in a gradient also seems odd.

  • Strawman 1:

    • Recognize that the existing 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 ≥ 1

    • steps(n, end) — n ≥ 1

    • steps(n, inside) — n ≥ 2 (= frames(n), i.e. the steps are inside the interval)



      • See next comment where Amelia suggests inclusive for this.



    • steps(n, outside) — n ≥ 1 (neither endpoint is included)



      • Alternatively exclusive (see next comment).



  • Strawman 2:

    • Forget trying to be consistent with start and end and try something that might be less mathematically consistent but is more intuitive.

    • steps(n, start) — n ≥ 1

    • steps(n, end) — n ≥ 1

    • steps(n, distribute) — n ≥ 2 (= frames(n), i.e. "distribute the steps evenly within the interval")

    • Alternatively, space (space apart)? equal (equally distributed)?

    • steps(n, clip) — n ≥ 1 (neither endpoint is included, i.e. clip off the endpoints)

    • Alternatively, trim (trim the gap between the steps and interval ends)? align (align steps with ends of interval)

  • Strawman 3:

    • As with the previous two examples but using a separate function name. Using a separate function name might be appropriate since the range of 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.

    • 3a. steps-inside(n) with n ≥ 2, steps-outside(n) with n ≥ 1

    • 3b. steps-inclusive(n) with n ≥ 2, steps-exclusive(n) with n ≥ 1

    • 3c. steps-distribute(n) with n ≥ 2, steps-clip(n) with n ≥ 1

    • etc.

Maybe 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):
Timing functions visualized

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:

  1. A one-shot animation not intended to fill.
  2. A repeating animation that doesn't end where it started.

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:

  1. A one-shot animation not intended to fill.
  2. 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.

The full IRC log of that discussion
<dael> Topic: reconsider name of frames() timing function

<dael> Github: https://github.com/w3c/csswg-drafts/issues/1301

<dael> brian: We introduced a new timing function where the difference is it includes both endpoints. It has a different name and different not ation so it counts number of steps. That made sense from usability pov though it's different than step timing

<dael> brian: There's two complexities. The timing functions in other content and other gradients which may need further variations. I'm wondering if we should extend steps or have something more familiar.

<dael> TabAtkins: We should continue this on issue.

<dael> Rossen_: Thank you Brian for the introduction to it.

<dael> Rossen_: Please jump to the GH issue and give your feedback there.

<dael> Rossen_: Next wednesday we'll resume with this.

<dael> Rossen_: Thanks Brian and please everyong give your opinion on the issue.

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.

Ok, since we're bikeshedding names now, what about discrete()?

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.

While I proposed it, I agree with the others that it's a pretty vague name. I'm still for extending the steps() function somehow. parts(), partitions(), fragments(), and segments() are similarly good, but none of them fits perfectly to all examples outlined by Brian.

Sebastian

This is almost certainly a dumb question, but why are all these special cases needed? Why can't the correct behaviour be deduced in the animation logic?

For example:

  • if the final and initial states of the animation would look the same to the user;
  • if the animation is going to repeat;
  • if the user asks for frames(N);
  • then divide into N+1 frames and display the first N for that run of the loop.

Do something similar looking at the final state (accounting for fill) on the the last run.

And automatically drop the first frame, on the first run, if it it resembles the state before the animation started.

Make frames() do the right thing out the box. If necessary, steps() can be extended for those rare cases when the right thing is wrong.

@fuchsia Automatically changing the behavior of a timing function based on the values of other properties is unlikely to be less confusing - it means you can't predict from looking at it how long each frame is! In particular, your suggested code would make the first/last iteration either have frames of different length than the rest of the animation, or have an iteration of different length from the rest of the animation. Both of these have a high chance of being bad due to things becoming unmatched.

Both Firefox and Chrome have decided to delay shipping frames() for the time being to allow this discussion to play out without being constrained by shipping implementations.

@tabatkins I think I thought user were being forced to make those decisions explicitly, with the bonus cognitive load of having to grok the issues in the first place. But I probably haven't understood it properly myself, so I'll stop digging (unless you want to use me to rubber duck debug the API.)

The CSS Working Group just discussed Name for frames() function., and agreed to the following resolutions:

  • RESOLVED: Renames frames to steps, argument name TBD.

The full IRC log of that discussion
<iank_> Topic: Name for frames() function.

<iank_> Github: https://github.com/w3c/csswg-drafts/issues/1301

<glazou> https://github.com/w3c/csswg-drafts/issues/1301

<iank_> birtles: So we specified a frames() timing function, makes sense for a usecase, animation that has discrete frames, and the start and end of the animation doesn't match, and the animatin repeats.

<iank_> birtles: And for that use-case, frames() is a very good way of describing the timing, very obvious name, the counting , the number that you put in that function is the number of frames, unlike steps() where you count the number of jumps.

<iank_> birtles: My concerns with frames, is

<iank_> birtles: 1) very good in that context, not suitable in other contexts, e.g. not a good name for gradients.

<iank_> birtles: 2) Having a different function name for something very similar can be confusing, e.g. frames() to a css transition, has almost idential result to steps() timing function

<iank_> birtles: likewise there has been a proposal for a 4th type of timing function where ... jumps happen at both ends of the interval.

<iank_> birtles: if we introduce that (seems likely) we'd do it as an extension to steps.

<fantasai> Illustration of the 4 options: https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-310571203

<iank_> birtles: means we'd have 4 timing functions, 3 named steps, one frames(), seems inconsistent

<iank_> birtles: Also frames() has bad discoverability, within devtools, (autocomplete)

<iank_> birtles: if you have a steps timing function, and doesn't quite look right, you'd just try and change that TF, not easy to "find" the frames TF instead.

<iank_> birtles: concered that they wouldn't discover frames, and then would have to use a different way of counting.

<glazou> s/concered/concerned

<iank_> birtles: My prefered way of doing this would be to extend steps.

<iank_> birtles: Even though we know that way of counting isn't ideal

<fantasai> birtles: for the particular use case of a repeating frame-based animation that doesn't go back to where it starts

<iank_> Rossen: So looking at the community twitter, that rachael did, frames() was solidly a preference from designers.

<iank_> fantasai: They are only concerned with what they do, not trying to make consistent with the rest of CSS

<iank_> fantasai: I agree with birtles points, agree that its easier to learn the set if its an extension to steps() instead of a new function, (frames())

<iank_> birtles: If you are animating a spinner, and its rotating, you'd want a steps TF, if you use frames() it'll double the length of the other points.

<fantasai> Was pointing out that polling people for their preferred syntax for that particular use case isn't going to take into consideration the interaction with the rest of the system, which is our job here in the WG.

<fantasai> For people who want a function to do the one thing that they're trying to do, yeah, frames() might be nicer. But in general people have to learn more than just the one case

<fantasai> and then for almost the same but not quite cases, they have to switch to steps()

<fantasai> which doesn't seem very helpful

<iank_> Rossen: So the current name in the spec is steps()?

<iank_> birtles: No frames()

<iank_> Rossen: Any other opinions? otherwise can resolve and move on....

<iank_> Rossen: No?

<iank_> Rossen: Any objections to renaming frames() to steps()?

<iank_> birtles: We'd still need to work out the name of the argument.

<iank_> birtles: Some of the proposals have been distribute.

<iank_> astearns: We could resolve on using steps, instead of frames, and work out param names later.

<iank_> fantasai: <showing examples on github issue>

<astearns> s/later/separately/

<iank_> fantasai: <https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-310571203>

<iank_> Rossen: Are there any objections to renaming frames() to steps()?

<iank_> fantasai: In favour.

<astearns> +1

<iank_> RESOLVED: Renames frames to steps, argument name TBD.

<fantasai> fantasai^: These are the four options under consideration for this set of timing functions. The first two exist already, the second two are proposed.

<iank_> fantasai: The next thing is that we have the 'start', 'end'... we need two more keywords.

<iank_> fantasai: We need to be clear of the distiction between the two new keywords.

<astearns> inside/outside, inclusive/exclusive also mentioned

<iank_> fantasai: Proposal to use the space keywords, which we use for distributing space, e.g. 'space-between', 'space-evenly'

<iank_> fantasai: Could also use 'space-around'

<iank_> fremy: space-around only has half of the space....

<iank_> fantasai: we could space-* keywords, could use another word other than space?

<iank_> birtles: could drop the space- prefix, e.g. between, evenly, etc.

<iank_> dino: I'd prefer that anyway, we are going to have to look this up anyway

<iank_> fantasai: At least you've learn't alignment, then we can transfer the knowledge to different area.

<tantek> I've read the entire github issue and I'm still not sure about which of the options are better.

<iank_> astearns: There is the option using the start, end syntax, to start, end, both, none.

<iank_> dbaron: start, end made the most sense for 1 step.

<iank_> <general talking about arg names>

<Bert> ('center' seems nice and short, transition in the middle rather than at the start or end...)

<iank_> Rossen: We can always continue this in the discussion.

<iank_> Rossen: We are already going against tabs strong pushback, it also doesn't sound like we have clear winners on arg names.

<iank_> birtles: Could we at least list the current candidates?

<iank_> birtles: 1) distribute and justify naming.

<iank_> Florian: 2) distribute and stretch

<iank_> birtles: 3) space-evenly, space-between

<iank_> dbaron: 4) center, stretch

<Rossen> steps( <integer>, [ trim-start | trim-end | trim-both | no-trim ]

<iank_> birtles: 5) both, neither

<iank_> Rossen: 5) trim-start, trim-end, trim-both, no-trim

<fantasai> fantasai: Problem with distribute and justify is that they are two words that mean almost the same, and aren't used to make a distinction anywhere else in CSS.

<iank_> Rossen: 6) trim-start, trim-end, trim-both, no-trim*

<fantasai> fantasai: (We also have text-justify: distribute as a legacy thing, which is really not helping here)

<iank_> birtles: Don't like the trim names b/c when you use them you typically don't drop the endpoints, you see the endpoints.

<iank_> Bert: 7) short, long.

<iank_> astearns: 8) include, exclude

<fantasai> fantasai: Problem with using center is that both space-around and space-evenly are centering methods, but they are different. It's not clear which one this is.

<fantasai> I really don't like 1/2/4/7.

<iank_> Rossen: If folks are passionate about this, they can take this offline, and come back to the group, I really want to include TabAtkins , unfortunate to re-resolve this later

<fremy> My pref would be 3 or 4

<iank_> Rossen: Lets close this, if we have a clear set of keywords by the end of the F2F, we can resolve on a later day.

Talking with @fantasai during the break, I like center , stretch (spread?). @fantasai likes space-evenly, space-between and is concerned that if we were to go with center, stretch and later decided we wanted a version that behaves like space-around we will wish we used the space-* naming to begin with.

The CSS Working Group just discussed step function parameter names, and agreed to the following resolutions:

  • RESOLVED: Use steps() function with the <integer> being number of visible frames during the animation duration

The full IRC log of that discussion
<tantek> topic: step function parameter names

<tantek> TabAtkins: strong objection to anything that resembles sizing or alignment keywords

<tantek> TabAtkins: they are extremely confusing

<tantek> TabAtkins: we have start and end already

<tantek> TabAtkins: imply dropping start or end step

<tantek> TabAtkins: we should come up with two keywords that fit that model

<tantek> astearns: one of the reasons I proposed both and neither

<tantek> Florian: it depends what what the step is, flat or verticle

<tantek> TabAtkins: flat

<tantek> fantasai: vertical

<tantek> s/verticle/vertical

<tantek> TabAtkins: I think for most authors the intuitive model is number of values received

<fantasai> ScribeNick: fantasai

<fantasai> birtles: If you have say, steps(2, start)

<fantasai> birtles: this is what you see as the timing function

<fantasai> birtles: If you're repeating this, you just see those two values, but if you apply this to a transition, you will see the pictures before and after the timing function

<fantasai> TabAtkins: The starting frame there isn't included in the value

<fantasai> birtles: But as an author you're seeing all three values

<fantasai> TabAtkins: if ou say I want transition to last 1s, and steps(2) and the time of the steps don't last .5s each, then it's bad

<fantasai> birtles: I disagree

<fantasai> birtles: We've already resolved we're sticking with steps() and the number is the number of jumps

<fantasai> https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-310571203

<fantasai> TabAtkins: The argument from RachelNabors is that you can't accurately time a tansition or animation that during the preiod shows both the start and end value

<fantasai> TabAtkins: because right now you have to, during the transition, either the starting value is not shown and you jump immediately or

<fantasai> TabAtkins: During a single transition, if you want to see both start and end there, which is a use case RachelNabors brought up, you have to do some non-intuitive duration-manipulation to make it show correctly

<fantasai> TabAtkins: If you want a 3-step frame-based cartoon that lasts 3s then...

<fantasai> birtles: We're talking cross-purposes

<fantasai> birtles: We're all agreeing that this timing function exists

<fantasai> birtles: we're just talking about the syntax for it

<fantasai> birtles: We went over the number of steps this morning

<fantasai> birtles: and resolved on that aspect

<fantasai> dino: I think we agree that there are three steps there, if we're going up a staircase, there are three steps there.

<fantasai> fantasai: We're off-topic, the topic is the name of the keywords not the syntax of the function overall. That was resolved already.

<fantasai> TabAtkins: I object to that

<fantasai> fantasai: oesn't matter, you're off-topic

<fantasai> TabAtkins: There are 4 things being shown !

<fantasai> dino: 4 things shown is 3 steps

<fantasai> TabAtkins: You're thinking about the wrong things.

<fantasai> [rehash of arguments]

<fantasai> TabAtkins: I'm representing RachelNabors, nobody else is!

<fantasai> birtles: I presented the arguments on both sides, and explained why I think that the steps() syntax is the right way to go

<fantasai> birtles: Didn't go with frames() for two reasons

<fantasai> birtles: First is, it's less appropriate for contexts outside of looping animations

<fantasai> birtles: transitions and gradients being examples

<fantasai> birtles [gives more specific examples]

<fantasai> birtles: The other concern was discoverability

<fantasai> birtles: If you start with steps() and it's not quite right, then can't discover frames() behavior by adjusting arguments, and also need to change how you count

<fantasai> birtles: or vice versa

<fantasai> fremy: Your problem is that you count 3 here, what if instead we count 4, but keep it in the steps() function

<fantasai> TabAtkins: If the third one was steps(4, something) and the fourth one was steps(2, somethingelse) then it's fine.

<fantasai> fremy: So you don't object to using steps(), just object to using 3.

<dbaron> dino: So you're counting the number of places you put your foot rather than the number of times you go up.

<fantasai> TabAtkins: Want it to be number of things you see

<fantasai> gregwhitworth: Rachel does say that she's fine to re-use steps().

<ericwilligers> +q

<fantasai> birtles: The tricky bit is that in the first two cases, which we have right now, it just so happens that the number of values you see also matches the number of jumps

<fantasai> birtles: That's why that happens to be okay

<fantasai> birtles: But that's only true when your'e repeating

<fantasai> birtles: If you're transitioning or using animation-fill-mode, then you see more

<fantasai> TabAtkins: But that's not part of the duration

<fantasai> dino: Tab only cases where yo put your foot between the start and the end, doesn't care about what's before or after the start/end

<fantasai> ericwilligers: If we go with wher eyou put your foot, then the bottom left would be steps(4, neither) and the bottom right would be steps(2, both)

<dbaron> referring to the diagram in https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-310571203

<fantasai> TabAtkins: Your'e showing neither start nor end during the duration

<gregwhitworth> ^ for reference - here is the comment where RachelNabors

<fantasai> TabAtkins: I think it would be better to have drop-* keywords or something

<gregwhitworth> ^ says that: https://github.com/w3c/csswg-drafts/issues/1301#issuecomment-299749586

<fantasai> TabAtkins: but then you'd have to ...

<fantasai> [mumbling in the corner]

<fantasai> dbaron: I think start and end makde a lot of sense for step-start and step-end

<fantasai> [but maybe less so here]

<TabAtkins> TabAtkins: I agree. They are the correct start/end pairs for single steps.

<fantasai> astearns: So I've heard people say they don't like the idea of having the bottom left be steps(4) and the bototm right be steps(2), but does anyone object to the idea of counting places to put your foot?

<fantasai> TabAtkins: The other people talking about other interpolation stuffs also want 4 and 2

<fantasai> TabAtkins: So I think they're intuitive on their own

<fantasai> birtles: This is a frame based animation here

<fantasai> birtles projects a demo

<fantasai> birtles writes

<fantasai> animation: slide 3s infinite steps(10, start)

<fantasai> birtles: You see you miss frame 1

<birtles> http://slides.com/birtles/browser-animation-2017/live#/3/2

<fantasai> birtles: If I s/start/end, I start at 1, but don't se 10

<fantasai> birtles: If I say frames(11), then I get all the 10 frames

<fantasai> birtles: But to get this effect, I have to change the number

<fantasai> ...

<fantasai> birtles: If you're switching between the two, then you have to change the number

<fantasai> dbaron: Seems like we have one solution that isn't drawing strong objections from people

<fantasai> dbaron: which is to use the steps() function with two new keywords, and count the number of places you put your foot

<fantasai> Bert: Referring back, if you count the number of intermediate steps, there's only 9 intermediate steps

<fantasai> Bert: Some variations, you only see those 9 steps, the first and last are outside the animation itself

<fantasai> [everybody picks a number to shout]

<fantasai> TabAtkins: Using values 10 11 or 9 with some yet-undecided set of keywords, using steps() function name, seems to be drawing least objections.

<fantasai> TabAtkins: Anyone object to that, assuming good keyword names

<fantasai> dbaron: We need to find names that work with the numbers

<fantasai> astearns: Any objections to this proposal?

<dbaron> github: https://github.com/w3c/csswg-drafts/issues/1301

<fantasai> astearns: So we'll add to this morning's resolution that we'll use steps() function with numbers that count the place where you put your foot

<fantasai> astearns: I'm concerned that this will prove confusing

<fantasai> Bert: If you allow 11 here, then 1 makes no sense

<fantasai> Bert: then why not lower the number, and say it's the number of intermediate values

<fantasai> ...

<fantasai> dbaron: Bert is concerned about the allowed range of integers, which will vary depending on the keyword

<dbaron> 1- for the existing keywords, and 0- and 2- for the new keywords

<fantasai> RESOLVED: Use steps() function with the <integer> being number of visible frames during the animation duration

<fantasai> astearns: What should the keywords be

<fantasai> fantasai: If we were starting from scratch I have an ida

<dbaron> fantasai: ... we'd use the same kind of: fill, start-fill, end-fill, and ... we have this concept of fill.

<TabAtkins> dbaron, actually I think the "intermediates only" is also 1+, not 0+. You need to have at least one "intermediate" value to show!

<TabAtkins> dbaron, so the frames() use-case is 2+, the rest are 1+.

<fantasai> fill-start, fill-end, fill-between, fill-evenly

<fantasai> just like alignment

<dbaron> TabAtkins, actually, maybe the new ones are both 2+

<fantasai> fill is time that's filled by a frame

<TabAtkins> dbaron: Nah, "intermediate only" is fine to be 1 - it shows only the 50% value during the animation.

<fantasai> TabAtkins: I'd be ok with 4 new keywords to make a set

<fantasai> s/dbaron:/dbaron,/

<fantasai> TabAtkins: rather than adding to start+end

<fantasai> astearns: What if keywords were talking about where to jump

<dbaron> astearns: You could have the keywords say where you jump. So you have steps(3, start), steps(3, end), steps(4, neither), or steps(2, both).

<fantasai> fremy: 4th one is intermediate only, only shows intermediate only

<fantasai> fremy: 3rd one includes ...

<fantasai> fremy: open set and closed set

<fantasai> fremy: intermediate and

<fantasai> TabAtkins: intermediate is over the desired spelling level

<fantasai> dino: Anyone wants 4th one?

<fantasai> TabAtkins: Yes, definitely have requests for that

<dbaron> Florian: _|¯

<fantasai> TabAtkins: esp for gradients

<Florian> steps(4, _-¯) steps(2, -)

<TabAtkins> linear-gradient(red 20%, steps(5, middle-only), blue 80%) <= 5 color stripes between the red and the blue

<fantasai> fantasai: So both and neither was ...? No, it's really confusing.

<fantasai> TabAtkins: As dbaron was saying, the start/end keywords make sense for the step-* keywords but not so much for the steps() function

<dbaron> Just as long as we don't use steps() and stairs() with two different meanings...

<SimonSapin> steps("▁▃▅▆▇███")

<fantasai> Florian: We do both and neither

<dbaron> Tab: show-end, show-start, show-both, show-neither

<fantasai> fremy: So both is show both start and end, and neither is ..

<fantasai> TabAtkins: No, that's backwards

<fantasai> TabAtkins: because of the way start and end are defined

<fantasai> fremy: screw it, we should do it this way around anyway

<fantasai> TabAtkins: that's okay if we have a prefix, like show-start or

<dbaron> (insert my previous line here)

<fantasai> astearns: So show-end is the same as start

<birtles> steps(4, frames)? (Not sure about the fourth option)

<fantasai> TabAtkins: if we do show keywords we do it this way, if we do drop kewyrods we can match start/end

<fantasai> TabAtkins: But it also was pointed out that drop has some weird impliciations of dropping a frame

<fantasai> TabAtkins: show-* doesn't have that problem or one of adding a frame

<fantasai> dbaron: stripes? :)

<dbaron> (somebody said before me that frames was for animations and the other one was for gradients)

<fantasai> birtles: steps(4, frames) steps(2, stripes)

<fantasai> dbaron: though they all give stripes, just a different set of stripes

<fantasai> fantasai: Oh, I like that Bert. That's better than using start/end with opposite meanings

<melanierichards> +1 to Bert's idea

<Bert> (Putting it on the record: just thinking aloud, but how about: step(n [, show-first || show-last]?) )

<fantasai> although I'd still have both keyword, because it's easier to type

<dbaron> actually 0 does work as the number for the fourth option

<fantasai> and also, how do you get the 4th variant?

<fantasai> Rossen and Tab discuss some examples

<fantasai> TabAtkins: Let's go back to thread with what we've concluded and ask for help

<fantasai> astearns: maybe we should open a new issue

<fantasai> astearns: OK, let's close this issue and open a new one on the new keywords

<TabAtkins> Bert, can't do that one unfortunately, because the "no keyword" case (steps(3)) already means steps(3, start). :(

<TabAtkins> s/steps(3, start)/steps(3, end)/

<fantasai> https://github.com/w3c/csswg-drafts/issues/1609

<fantasai> we could do 1609 but ppl might be fried

<fantasai> sorry https://github.com/w3c/csswg-drafts/issues/951

<fantasai> https://github.com/w3c/csswg-drafts/issues/951#issuecomment-316535854

Was this page helpful?
0 / 5 - 0 ratings