New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-timing-2] Complex timing functions #229

Open
rachelnabors opened this Issue Jun 24, 2016 · 51 comments

Comments

Projects
None yet
7 participants
@rachelnabors

rachelnabors commented Jun 24, 2016

In light of the Webkit team's implementation of spring(), it's apparent we need to attend to the issue of complex timing functions sooner rather than later.

The problem

Designers often need more advanced timing functions than can be described with cubic-beziers. They are not limited to spring functions, either. A common problem is there is no effective way to export a timing graph from Adobe After Effects to a timing function that could be used with CSS or with the Web Animations API. Currently designers have to hack together individual timing functions using CSS animation keyframes, which is impossible to do by hand in all but the most trifling instances.

spring() is just a bandaid.

The solution

We need a format to write functions like spring() in, one that we can export to from software like AfterEffects and prototyping tools that have yet to be built.

I am not in a position to propose the technical specifications of this solution. But there are people who have that knowledge. I have invited them (@visiblecode) to share their proposals below.

@birtles

This comment has been minimized.

Contributor

birtles commented Jun 24, 2016

There is some previous discussion of this topic, thread starting here: https://lists.w3.org/Archives/Public/public-fx/2015JulSep/0034.html

@visiblecode

This comment has been minimized.

visiblecode commented Jun 24, 2016

Cubic beziers aren't a bad choice for timing functions. They're very easy to evaluate (just a few additions and multiplications). Also, most animation tools use some form of cubic spline to specify animation paths, which can be converted into cubic bezier easings with good fidelity.

There are two problems with cubic-bezier() as it is currently specced, however:

  1. You can't specify a spline of multiple segments
  2. cubic-bezier() is abusing 2d parametric curves to specify 1d functions

The first issue can be addressed by, essentially, just adding more points to the cubic-bezier() syntax. I believe this has already been proposed in various forms.

The second issue is pretty significant, though, and deserves some unpacking. cubic-bezier() is specified as a 2d bezier, a 2d parametric curve. This has a lot of implications, including:

  1. Without additional constraints, the resulting curve isn't guaranteed to have a singular y value for a given x and can't be used to define a mathematical function. This has been dealt with by restricting x to the range [0, 1], but that doesn't actually address the root (no pun) problem -- because a cube root is involved there are still two extra solutions in the complex plane.
  2. As mentioned, implementors must solve cube roots to work backwards from x to the implicit t parameter, before they get to the normal (and much simpler/exact!) bezier calculation to evaluate the easing function.
  3. Tool authors who want to do a direct conversion of cubic spline segments to cubic-bezier()s have no use at all for the extra dimension, but must clutter the output with decimal approximations of 1/3 and 2/3.

The extra dimension isn't useful and makes life harder for implementors and tool authors alike. Ideally, instead of having to specify a two-dimensional curve like cubic-bezier(0.33333, 0.4, 0.66666, 0.6), we ought to have gotten one-dimensional curves, e.g. cubic-bezier(0.4, 0.6).

With one dimension, instead of having to work backwards from (normalized) time to an implicit t, the normalized time could be used directly as the input without having to solve cube roots first.

I guess if I were going to make a concrete proposal, I'd like to see syntax for specifying 1d bezier splines, in a format something like cubic-spline(c, [c,t,c,]* c) (where the cs and ts are numbers).

The first and last values are exactly like the control points in cubic-bezier(), except since we're only dealing with one dimension, the "control points" are specified by scalar values, rather than pairs of them.

Between those end "control values", you could have zero or more (control value, time, control value) triples representing intermediate knots with their adjacent control "points". The knot times (the middle value of the triples) would have to be monotonically increasing, in the range (0, 1).

As a concrete example, let's take the following complex easing:

 @keyframes example {
     from {
         some-property: 100px;
         animation-timing-function: cubic-bezier(0.33333, 0.25, 0.66666, 0.8);
     }
     50% {
         some-property: 75px;
         animation-timing-function: cubic-bezier(0.33333, 0.5, 0.66666, 0.6);
     }
     to {
         some-property: 50px;
     }
 }

Given the aforementioned cubic-spline() function, you could define the same animation more simply as:

 @keyframes example {
     from {
         some-property: 100px;
         animation-timing-function: cubic-spline(0.125, 0.4,0.5,0.75, 0.8);
     }
     to {
         some-property: 50px;
     }
 }

IMO this seems better for both export tooling and hand authoring.

One thing this proposal doesn't address are use cases where you want easings/animations with sudden jumps in them. (i.e. not C0 continuous) Having multiple intermediate CSS keyframes may still be a good answer for animations with discontinuities (rather than trying to define timing functions with discontinuities), though in order to do that there would need to be a mechanism to specify different incoming and outgoing property values for a CSS keyframe.

@visiblecode

This comment has been minimized.

visiblecode commented Jun 24, 2016

One of the things that bothers me about spring() specifically is that it requires finding an approximate solution to a differential equation, just like cubic-bezier() requires approximate solving of cube roots (only moreso). Browsers' approximations will vary, and there are performance/quality tradeoffs.

Using (1d) bezier splines for defining easing functions is far simpler than something like spring() computationally, and is easy to pull off with good precision. It's also relatively easy for tools to "bake" a physically-based animation (including ones much more complex than spring simulations) into a spline, which animation tools do anyway for better reproduceability and performance.

@visiblecode

This comment has been minimized.

visiblecode commented Jun 24, 2016

Thinking about it overnight, cubic-polybezier() might be a more accurate/less misleading name than cubic-spline(). I'll let other people bikeshed about naming though.

@visiblecode

This comment has been minimized.

visiblecode commented Jun 24, 2016

I'm also an idiot; those triples need to be four-tuples, there's an extra position parameter which I left out. So that example/proposal is wrong.

@visiblecode

This comment has been minimized.

visiblecode commented Jun 24, 2016

Adding the missing position parameter to the two control values, the syntax would be something like cubic-spline(c, [c, t, p, c,]* c). And that example becomes:

@keyframes {
    from {
        some-property: 100px;
        animation-timing-function: cubic-spline(0.125, 0.4, 0.5,0.5, 0.75, 0.8);
    }
    to {
        some-property: 50px;
    }
}
@visiblecode

This comment has been minimized.

visiblecode commented Jul 1, 2016

Having thought about this for a while and chatted with others, I think the minimum required features for representing complex easing functions boil down to:

Piecewise cubics are pretty much the common denominator for animation software, which either uses some form of piecewise cubic spline directly, or else curves which are reasonably easy to convert to piecewise cubics.

The main exception are animation tools which are doing physical simulations. Even then, some simple physical systems can be directly represented. For example, piecewise cubics are more than enough to precisely represent physically-accurate "bouncing ball" easings, since ballistic trajectories are simple parabolic arcs.

Oscillating spring (or pendulum) easings are sinusoidal and not exactly representable using simple polynomials, but you can still do a good job of approximating them by gluing multiple cubic segments together. If spring() became part of the standard, it might make sense to define it in terms of an appropriate piecewise cubic approximation.

I don't know how often jump continuities are really needed in easing functions, but they're certainly required if you want to be able to define a single easing function to recreate existing complex @keyframes animations which use timing functions like step-start() or step-end(). So it seems worth including them.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 1, 2016

It's probably worth observing that motion curves (as opposed to timing/easing curves) in animation software are a little bit of a different story. There you're more likely to find NURBS or other more sophisticated types of curves which require a bit more work to approximate with cubic segments.

On the 1d versus 2d issue -- From what I can see so far, After Effects uses one-dimensional piecewise functions for timing curves, although I don't have a copy to play with directly. However, poking at the implementation of Blender, it appears Blender f-curve segments do work similarly to cubic-bezier(), using (suitably constrained) 2d bezier curves to define the 1d function.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 1, 2016

Additional observation: while you can muddle through with multiple keyframes and cubic-bezier() easings for most purposes, if you need smooth curves joined by a jump discontinuity, @keyframes as currently specified can't be made to work. Best you can currently do to simulate such a jump discontinuity is to define two keyframes very close together and use a step easing between them:

@keyframes example {
    from {
        blah: 10px;
        animation-timing-function: cubic-bezier( ... stuff ... );
    }
    49.99999% {
        blah: 40px;
        animation-timing-function: step-end;
    }
    50% {
        blah: 100px;
        animation-timing-function: cubic-bezier( ... stuff ...);
    }
    to {
        blah: 150px;
    }
}
@birtles

This comment has been minimized.

Contributor

birtles commented Jul 1, 2016

Best you can currently do to simulate such a jump discontinuity is to define two keyframes very close together and use a step easing between them

Yes, this is actually quite common to see. The Web Animations API allows you to overlap keyframe offsets so that you can add discontinuities and I believe there has been discussion in the past of adding syntax to allow you to do this in CSS keyframes (e.g. 50%+).

@visiblecode

This comment has been minimized.

visiblecode commented Jul 1, 2016

One suggestion made in the Slack discussion, which I had promised to document here, was to use an SVG path to specify an easing function.

In that case, any SVG path could be allowed, at least provided it ranged between 0 and 1 in the x dimension, and the curve's x component was continuous and monotonic over that range as well. In that case, the M operator could used to indicate jump discontinuities.

So, for two cubic segments joined by a jump discontinuity 50% of the way through, you might have something like: animation-timing-function: path("M 0,0 C 0.17,0.1, 0.33,0.2 0.5,0.4 M 0.5,0.8 C 0.67,0.8 0.83,0.9, 1,1")

I have mixed feelings about this, but it would work, aside from the issue of needing to indicate whether you wanted left- or right- continuity at any jumps.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 1, 2016

The idea I had floated prior to that was something like: animation-timing-function: complex-easing(cubic(0, 0.1, 0.2, 0.4), 0.5, cubic(0.8, 0.8, 0.9, 1))

(In spite of cubic() having a superficial resemblance to cubic-bezier(), the parameters are all y values.)

i.e. two one-dimensional cubic bezier segments which meet at x = 0.5, but the first ends at y = 0.4, and the second begins at y = 0.8. Still doesn't address the issue of directional continuity though.

It does also require you to repeat the y/output value even when you want the two segments to join with C0 continuity. For example if the first segment ended at y = 0.4, and the second began there:

animation-timing-function: complex-easing(cubic(0, 0.1, 0.2, 0.4), 0.5, cubic(0.4, 0.8, 0.9, 1))

This also wouldn't allow for a direct translation of blender f-curves, while I think SVG paths would.

@rachelnabors

This comment has been minimized.

rachelnabors commented Jul 4, 2016

This also wouldn't allow for a direct translation of blender f-curves, while I think SVG paths would.

That concerns me. Also, I can't imagine how to write a script to export to this format from a motion graph in, say, After Effects.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 5, 2016

There's a meta issue here in that there's a bit of a mismatch between the way AE deals with animation and CSS to begin with. More specifically, animation-timing-function doesn't directly correspond to motion (v.s. timing) graphs in AE. It's more akin to the time remapping feature.

That being said, if you extract the x, y, etc. components from the motion separately (like the AE "separate dimensions" feature does), then you could turn them into separate animations with timing functions in the format I gave. But then CSS would need to provide a nice way to combine multiple transform: animations. (Today, you have to play tricks with nested divs.)

@birtles

This comment has been minimized.

Contributor

birtles commented Jul 6, 2016

But then CSS would need to provide a nice way to combine multiple transform: animations. (Today, you have to play tricks with nested divs.)

We have that in CSS Animations 2: animation-composition

@grorg

This comment has been minimized.

Contributor

grorg commented Jul 14, 2016

So if we ignore the composition and what-i-call-triggers-but-might-be-also-called-chaining, and assume spring() is handled separately (#280), and see @rachelnabors's recent tweet, can we start by adding some hardcoded shortcuts?

The majority of those on easings.net are variations of a cubic-bezier. If these are really useful, and if other implementors agree, we can add keywords for them.

Unless I've missed some, the functions on easings.net that are not supported by CSS are:

  • easeInElastic
  • easeOutElastic
  • easeInOutElastic
  • easeInBounce
  • easeOutBounce
  • easeInOutBounce

How popular are these? The first two are very much like spring().

Looking at tools...

After Effects only seems to have a couple of built-ins, which it calls "easy ease". However, it allows you to manually create some pretty complicated curves.

Apple's Motion does things in two different ways. It has "Behaviours" which are animation effects that you don't really see as keyframes and easing (more like "move in this direction with this speed and friction"). For the traditional keyframe animations, it has a manual editing mode like After Effects, but some shortcuts for bezier, linear, exponential, logarithmic and continuous.

Cinema 4D has basic ease in/out/both, linear and steps. It also has some tooling for smoothing keyframes (e.g. smooth tangents) which would likely produce curves that we couldn't exactly match in CSS at the moment.

Can people provide other examples?

@notoriousb1t

This comment has been minimized.

notoriousb1t commented Jul 14, 2016

@visiblecode. While I understand the hesitance to create an easing path function similar to SVG paths, I think it would make easings like GSAP's RoughEase.ease easier to do for tool makers.

http://greensock.com/ease-visualizer and click on Rough

@vidhill

This comment has been minimized.

vidhill commented Jul 18, 2016

Hi,

I know there are two different types of animation being discussed in this, predetermined animation curves and programmatic animations, like Apple's spring.

I've been thinking about the latter and I think I have an idea.

What if we could facilitate scripted animation for transition values.

Here is the proposition I have arrived at..
I think it actually looks similar to how Houdini code will look, but I haven't delved deply into that as yes.

Anyway, your css would look something like this

.my-element {
    /* transition: transform 500ms url('../simple.js'); */
    transition: transform 500ms url('../bounce.js')(1 100 10 0);
    transform: translate(0px, 0px);
}

.my-element.active {
    transform: translate(200px, 200px);
}

Simple example of an ease function file

File: simple.js

/**
 * The simplest possible easing function, linear
 */
export init function(){
    //in this case init does nothing
}

/**
 * Function that gets called every frame until a done() callback / promise.resolve()
 * @param {float} t - Transition current Time, value from 0 to 1
 * @param {Promise} - Promise that gets resolved when animation is complete
 * @return {float} A value 0 is not transitioned at all and 1 is fully transitioned
 */
export frame function(t, animationComplete){
    if(t === 1){
        animationComplete.resolve('done');
    }    
    return t; // linear, very boring..
}

Example of how something more complex, e.g. Apple's bounce transition effect could be defined using this method

File: bounce.js

/**
 * Simulate a spring using the solving algorithm defined by this JavaScript
  function
 * @param {float} The mass of the object attached to the end of the spring. Must be greater
  than 0. Defaults to 1.
 * @param {integer} The spring stiffness coefficient. Must be greater than 0.
  Defaults to 100.
  * @param {integer} The initial velocity of the object attached to the spring.
  Defaults to 0, which represents an unmoving object..
  Defaults to 10.
   * @param {float} initialVelocity  
 */
export init function((mass, stiffness, damping, initialVelocity)){
    // code that need to be run once during initialization
    // -real code 
}

/**
 * Fuction that gets called every frame until a done() callback / promise.resolve()
 * @param {float} t - Time, value from 0 to 1
 * @param {promise} - Promise that gets resolved when animation is complete
 * @return {float} A value from 0 to 1+ where 0 is not transitioned at all and 1 is fully transitioned, in the case of spring the value overshoots 1 initially then eventually settles on 1 
 */
export frame function(t, animationComplete){
    // -real code
    // -real code
    return result;
}

The browser would know up front about the function and could, I assume be prepared,

Different variations of the spring, for example could be achieved by passing different values into the init function, which could be done from the CSS, no need to touch the js.

Obviously some code savvy people could share their functions with the community, and feasibly come up with some very clever stuff. And it would not need to go through the standards process, in the spirit of the Houdini project

The frame function would at maximum be executed every frame,
But of course the browser could decide to drop/skip frames if it needed,

The browser itself could work out how much rounding would happen.
The only thing the script could do is return a value 0-1 obviously overshooting 1 if the ease necessitated

@rachelnabors

This comment has been minimized.

rachelnabors commented Jul 19, 2016

Interesting perspective, @vidhill. Thanks for sharing. I am personally a bit hesitant to wait to see how Houdini's adoption goes and what hiccups will come down the line when we could nail down a spec today. But if we can't, you very well may have described the future.

@grorg Thanks for joining the conversation! I'd love to see browsers offer more defaults than just ease-in, linear, etc. I was thinking adding easeOutQuint and the like. That's probably for another discussion, though, as we're chatting more about how to make a robust timing function that could underly other timing functions (like spring() and steps() ) and possibly be an export format for programs like After Effects. I've seen a real need from designers, as I know you have. What do you think?

@notoriousb1t

This comment has been minimized.

notoriousb1t commented Jul 19, 2016

It sounds good in theory, but I wonder if making a separate network call for each easing is a good idea. Even if you banked on HTTP2 to deliver it with the CSS file as an additional resource, it still would make CSS dependent on JS or a JS subset.

@vidhill

This comment has been minimized.

vidhill commented Jul 19, 2016

@rachelnabors just to clarify this wouldn't be a proposal for something that would be necessarily be a part of the Houdini spec.
I doubt it would be necessary for Houdini to work out to implement this.

I mentioned Houdini because I imagine that it'd this would be preferable for it to align with the style/spirit.

@notoriousb1t I understand the concern, the first thing that comes to mind is the question, what if the user has js disabled!?
As this js would not be allowed to do any direct dom manipulation (or anything else), all it should do is return a number
Would a different rule be able to be applied to this js?

It shouldn't be capable of doing anything 'nasty'

@birtles

This comment has been minimized.

Contributor

birtles commented Jul 20, 2016

There's a long-term plan to support script-defined timing functions but we're waiting on certain houdini components to materialize first. I think the current name of the piece we're missing is a worklet -- basically we want a bit of script that runs with very limited context and no side effects that we can run on either the main thread or compositor.

@vidhill

This comment has been minimized.

vidhill commented Jul 20, 2016

@birtles I had a small chat about this on twitter with 'surma' and a few others,
I got some ideas on how I could re-do my example to be more inline with the Houdini conventions, and Surma recommended I send it in to the Houldini mailing list.

If this is already on the long term plans is the a point to doing this?
Would I be adding anything novel to the discussion?

-I'm new to the standards process, so genuinely don't know

@birtles

This comment has been minimized.

Contributor

birtles commented Jul 21, 2016

@vidhill I think you should start a new issue for script-generated animations. This issue is pretty specifically about expanding the scope of timing functions that can be specified in a declarative manner. You could make that issue on this repository or on the web-animations one although I suspect it might be easier to start with Web Animations (since it is the lower level spec and already has a script API) and then we can layer CSS syntax on top later. In that issue, code examples using Houdini worklets would be useful.

@vidhill

This comment has been minimized.

vidhill commented Jul 21, 2016

Thanks,
will do that, and I will update my code examples.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 30, 2016

So, to focus this a little more, roughly what we need is this:

  • A way to define a timing function as a sequence of simpler functions, along with the breakpoints (as time in the 0 - 1 range) where one function hands off to the next.
  • Probably using some kind of cubic bezier or quasi-bezier representation for the simple functions (could even literally be cubic-bezier(), at which point you might as well also allow any of the preset easings)
  • With a way to specify which segment "wins" at a breakpoint between two segments (similar to "start" v.s. "end" with steps)
  • That fits the spirit of existing CSS syntax

@keyframes is actually trying to accomplish roughly this, but it doesn't let you express jump discontinuities, it's building a whole animation rather than just a timing function, and it makes you explicitly calculate all the intermediate property values at every keyframe.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

So, there are two ways forward:

  1. fix @keyframes to allow jump discontinuities and let the browser calculate the property values for intermediate keyframes
  2. introduce a new animation timing function syntax which lets you express a sequence of functions (maybe even just other timing functions) with the above characteristics

Either one should be sufficient for export from animation tools.

@notoriousb1t

This comment has been minimized.

notoriousb1t commented Jul 31, 2016

I think option 2 would provide a more obvious way to reuse timings with a transpiler and would make it accessible to css transitions.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

So, how about something like this:

animation-timing-function: cubic-bezier(...), at 50% cubic-bezier(...), after 80% cubic-bezier(...);

(Using ellipsis so I don't need to think about inventing cubic-bezier parameters.)

The first cubic-bezier easing is time-scaled to the (0% to 50% time range), the second easing kicks in at exactly 50% time (and is time-scaled to the 50% to 80% time range), and the third easing kicks in just after 80% time (and is time-scaled to the 80% to 100% time range).

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

Actually that still doesn't adequately cover jump discontinuities. I'm forgetting that all the easing function outputs are constrained to be 0 at 0% and 1 at 100%, even though they're allowed to overshoot in between.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

could do something like this (and show some stock easing functions in addition to cubic-bezier):

animation-timing-function: ease-out range(0, 0.3), at 50% ease-in range(0.3, 0.1), after 80% cubic-bezier(...) range(0.6, 1.0);

@notoriousb1t

This comment has been minimized.

notoriousb1t commented Jul 31, 2016

I need to test, but I think you can do steps(1,end) between two identical keyframes and it should stop animation for that time period.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

the idea with range() is that it'd rescale the output of the simple timing function from 0.0 -> 1.0 to whatever you specify.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

I should live up to my name for a change; here's a visual example.

Given:

animation-timing-function: ease-in range(0.0, 0.3), at 40% ease-out range(0.3, 0.1), after 70% cubic-bezier(... stuff ...) range(0.6, 1.0);

You'd get an animation timing function which graphs like this:

graph

I think this accomplishes everything we've talked about wanting upthread.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

The grammar would look something like:

<timing-function-segment> = <single-timing-function> [range(<number>, <number>)]?
<timing-function-segment-start> = [at | after]? <percentage>
<timing-function-extra-segment> =
    <timing-function-segment-start> <timing-function-segment>
<timing-function> =
    <timing-function-segment> [, <timing-function-extra-segment>#]?

If at/after is omitted before the percentage, at is assumed.

If range(...) is omitted, the segment's input (time) interval determines the output range.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

The thing with range() being optional is mostly so you can keep using the existing syntax:

animation-timing-function: ease-in;

rather than having to explicitly write:

animation-timing-function: ease-in range(0.0, 1.0);

But it also means you can just chain a bunch of easings and get a continuous result, for example:

animation-timing-function: ease-in, 30% ease-in-out, 70% ease-out;

and get something equivalent to:

animation-timing-function: ease-in range(0.0, 0.3), 30% ease-in-out range(0.3, 0.7), 70% ease-out range(0.7, 1.0);

So it's actually not bad for hand-authoring in simple to medium cases.

@visiblecode

This comment has been minimized.

visiblecode commented Jul 31, 2016

(But the most important thing is still that tools should be able to reasonably export curves as a sequence of cubic-bezier() easings in this form.)

@visiblecode

This comment has been minimized.

visiblecode commented Aug 1, 2016

To clarify, since it's been asked -- at|after is determining where the previous segment ends and the new one begins along the horizontal axis. range() is determining the vertical range for a segment, and whether it's increasing or decreasing.

For very simple use cases (like backwards-compatibility with today's syntax), the two would sometimes be directly related (and range() becomes superfluous), but this wouldn't be the case in general.

@vidhill

This comment has been minimized.

vidhill commented Aug 2, 2016

@visiblecode your train of thought all makes sense

@Martin-Pitt

This comment has been minimized.

Martin-Pitt commented Aug 2, 2016

How does this look like when you have two or more animations and defining timing functions individually for each animation? Just checking that the comma syntax doesn't look too weird there, as it is usually used to separate different pairs/sets, e.g. like you can with multi background props.

@visiblecode

This comment has been minimized.

visiblecode commented Aug 8, 2016

@Martin-Pitt Ohh, that's a good point. Using commas in the syntax for a single (compound) easing function won't work, because it'd be ambiguous when used that way.

@visiblecode

This comment has been minimized.

visiblecode commented Aug 8, 2016

Let's say we strip down my original syntax proposal a little bit:

<timing-function-segment> = <single-timing-function> [<number> <number>]?
<timing-function-segment-end> = [until | through]? <percentage>
<timing-function-extra-segment> =
    <timing-function-segment-end> <timing-function-segment>
<complex-timing-function> =
    <timing-function-segment> <timing-function-extra-segment>*
<timing-function> = <complex-timing-function>#

This definition of <timing-function> is more compatible with the existing definition, which has used commas to support the multiple animation use case which @Martin-Pitt pointed out.

To reduce the verbosity a little, I've also replaced range() with a simple pair of numbers. "at" and "after" have also become "until" and "through", since without the commas it seems to read better if things are worded in terms of the end of the preceding segment instead of the start of the following one.

So with this syntax, my earlier example:

animation-timing-function: ease-in range(0.0, 0.3), at 40% ease-out range(0.3, 0.1), after 70% cubic-bezier(... stuff ...) range(0.6, 1.0);

would instead be:

animation-timing-function: ease-in 0.0 0.3 until 40% ease-out 0.3 0.1 through 70% cubic-bezier(... stuff ...) 0.6 1.0;

@visiblecode

This comment has been minimized.

visiblecode commented Aug 8, 2016

"until" is a little bit of a tricky word in English, because depending on context it may or may not be inclusive. (Here, I'm using "until" to mean an exclusive time bound for a segment, and "through" an inclusive one.) Would like some better suggestions for words, if anyone has ideas.

@visiblecode

This comment has been minimized.

visiblecode commented Aug 8, 2016

Also, if the output range is not given for a segment, it's probably better to default to using the starting/ending output values of the neighboring segments, if those are explicitly given. That would make hand-editing nicer.

If a neighboring segment doesn't have an explicit output range, or if there isn't a neighboring segment on one side, then a segment can fall back to taking a starting/ending output value from its starting/ending percent time.

@grorg

This comment has been minimized.

Contributor

grorg commented Aug 22, 2016

I'm jumping in late, but I'd like to point out that script-based timing functions should be limited to script-based animations (e.g. Web Animations).

The trick with animations, in particular CSS animations, is that the browser knows up front exactly what the animation is. This means it can easy do the animation in the compositing thread (or process), without impacting the main thread (UI). Script-based timing functions invalidate this opportunity, since there is no guarantee on the amount of time the function takes to compute. Yes, you can reduce the side-effects by putting the timing function in an isolated Houdini world, but that still doesn't stop an infinite loop.

This doesn't mean I'm against script-based timing functions (although I do think it in general it is more common to script the entire animation rather than just the timing function part). I just want to make it clear that a simple declarative time-bounded function is necessary, and probably solves 99% of use cases.

@rachelnabors

This comment has been minimized.

rachelnabors commented Aug 29, 2016

I'm with @grorg on this one: simple, declarative, time-bound, a function to round out timing functions in the spirit of cubic-beziers, just with more flexibility for exporting and creating future declarative timing functions.

@birtles

This comment has been minimized.

Contributor

birtles commented Sep 19, 2016

Just a few thoughts here:

  • I don't know how important it is to worry about discontinuities here. It might be that it's sufficient to handle that using keyframes (assuming we'll eventually add a nicer syntax for overlapping keyframes such as 50%+ { ... }). Certainly if it complicates the syntax too much I'd suggest leaving it out.
  • I like the idea of a 1d function. Solving the value for t before you can evaluate the function is a pain.
  • Supporting SVG syntax sounds like overkill.
  • The solution @visiblecode was moving towards seems very similar to this one here (item 4). Let's call it the chaining approach.
  • The other major alternative being thrown around is one-big-function approach referenced (a) in the web animations spec, and (b) previously proposed to www-style by @AmeliaBR.
  • We still do get a lot of requests for script-defined timing functions. I'm not sure if we will expose this to markup but I think in any case the proposal would be to time box them. (Also, talking about this over lunch with @shans, if we assume these functions are stateless, then we might not even need worklets for this since implementations could presumably just sample the function at, say, a ~100 points and just linearly interpolate between the points although that wouldn't work for discontinuities which I think the do want for script-defined timing functions.)
  • Spring timing functions have the additional complication that you really would like the duration to be calculated from the springiness and distance of the animation. For this I think we can probably exploit the fact that the default animation duration in Web Animations is "auto" but I believe Google might not have another proposal for this.
@birtles

This comment has been minimized.

Contributor

birtles commented Feb 10, 2017

Moving this to [css-timing-2] since I believe that is where we will end up addressing this.

@birtles birtles changed the title from [css-transitions][css-animations] Complex timing functions to [css-timing-2] Complex timing functions Feb 10, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment