Skip to content
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-easing-1] Some ideas for linear() easing #6533

Closed
wants to merge 5 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 117 additions & 7 deletions css-easing-1/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,126 @@ The syntax for specifying an [=easing function=] is as follows:
<<step-easing-function>></div>


<h3 id=the-linear-easing-function oldids=linear-timing-function-section>The linear easing function: ''linear''</h3>
<h3 id=the-linear-easing-function oldids=linear-timing-function-section>The linear easing function: ''linear()''</h3>

The <dfn export lt="linear easing function|linear timing function">linear easing
function</dfn> is an identity function
meaning that its [=output progress value=] is equal to the
[=input progress value=] for all inputs.
A <dfn export lt="linear easing function|linear timing function">linear easing function</dfn>
is an easing function
that interpolates linearly
between its [=linear easing function/points=].

The syntax for the [=linear easing function=] is simply the
<dfn dfn-type=value for=easing-function>linear</dfn> keyword.
A [=linear easing function=] has <dfn for="linear easing function">points</dfn>,
a [=/list=] of [=linear easing points=].
Initially a new empty [=/list=].

A <dfn>linear easing point</dfn>
is a [=/struct=]
that has:

<dl dfn-for="linear easing point">

: <dfn>input</dfn>
:: A number or null

Note: The [=linear easing point/input=] is only null during the [=create a linear easing function=] algorithm.

: <dfn>output</dfn>
:: A number


</dl>

<section algorithm="to calculate linear output progress">

To <dfn export>calculate linear output progress</dfn>
for a given [=linear easing function=] |linearEasingFunction|,
and an [=input progress value=] |inputProgress|,
perform the following.
It returns an [=output progress value=].

1. If |linearEasingFunction|'s [=linear easing function/points=] [=list/is empty=],
then return |inputProgress|.

1. If |linearEasingFunction|'s [=linear easing function/points=] [=list/size=] is 1,
then return |linearEasingFunction|'s [=linear easing function/points=][0]'s [=linear easing point/output=].

1. Plot |linearEasingFunction|'s [=linear easing function/points=] points on a graph
using [=linear easing point/input=] as the x-axis
and [=linear easing point/output=] as the y-axis.

1. Interpolate the points linearly.

1. Extend the ends of the graph to infinity using the final angle of the line at either end.
If the line starts/ends with two points of the same position, the line should extend along the x-axis.

1. Return the y-axis value corresponding to the x-axis value of |inputProgress|.

</section>

<dfn function lt="linear()">linear(<<linear-stop-list>>)</dfn>

<pre class="prod">
<dfn>&lt;linear-stop-list></dfn> = [ <<linear-stop>> ]#
<dfn>&lt;linear-stop></dfn> = <<number>> && <<linear-stop-length>>?
<dfn>&lt;linear-stop-length></dfn> = <<percentage>>{1,2}
</pre>

<section algorithm="to create a linear easing function">

To <dfn>create a linear easing function</dfn>
from a <<linear-stop-list>> |stopList|,
perform the following.
It returns a [=linear easing function=].

1. Let |function| be a new [=linear easing function=].

1. Let |largestInput| be negative infinity.

1. For each |stop| in |stopList|:

1. Let |point| be a new [=linear easing point=].

1. [=list/Append=] |point| to |function|'s [=linear easing function/points=].

1. Set |point|'s [=linear easing point/output=] to |stop|'s <<number>>.

1. If |stop| has a <<linear-stop-length>>, then:

1. Set |point|'s [=linear easing point/input=] to whichever is greater:
|stop|'s <<linear-stop-length>>'s first <<percentage>> as a number,
or |largestInput|.

1. Set |largestInput| to |point|'s [=linear easing point/input=].

1. If |stop|'s <<linear-stop-length>> has a second <<percentage>>, then:

1. Let |extraPoint| be a new [=linear easing point=].

1. [=list/Append=] |extraPoint| to |function|'s [=linear easing function/points=].

1. Set |extraPoint|'s [=linear easing point/output=] to |stop|'s <<number>>.

1. Set |extraPoint|'s [=linear easing point/input=] to whichever is greater:
|stop|'s <<linear-stop-length>>'s second <<percentage>> as a number,
or |largestInput|.

1. Set |largestInput| to |extraPoint|'s [=linear easing point/input=].

1. Otherwise, if |stop| is the first [=list/item=] in |stopList|,
Copy link

@dshin-moz dshin-moz May 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, admittedly this is an edge case, but if any of the entries not at either edge contain negative or higher-than-100% percentage values (linear(0, 0.2 -10%, 0.8 110%, 1.0)), this would no longer be a proper function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the ordering enforced? So -10% would be clamped to 0, since there's a 0 beforehand?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true on the start side - how about the end, linear([...], 0.8 110%, 1.0)? Could use |largestInput|, though each side behaving differently seems a bit less intuitive (Debatable, since we're already in an edge case).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if the last value doesn't have a position, it should be assumed to be 100%. Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, let me test gradients…

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://jsbin.com/mopojul/4/edit?html,css,output

It seems like the first value is assigned 0% if it has no position. However, it seems like the last position will be Math.max(previousPosition, 100%).

My intent will be to spec the same position behaviour as gradients unless there's a good reason to do something different.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable. Thanks!

then set |point|'s [=linear easing point/input=] to 0.

1. Otherwise, if |stop| is the last [=list/item=] in |stopList|,
then set |point|'s [=linear easing point/input=] to 1.

1. For runs of [=list/items=] in |function|'s [=linear easing function/points=] that have a null [=linear easing point/input=],
assign a number to the [=linear easing point/input=] by linearly interpolating between the previous and next [=linear easing function/points=] that have a non-null [=linear easing point/input=].

1. Return |function|.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serializing back from this returned function may result in a different round trip result: linear(0 0% 50%, 1 50% 100%) -> linear(0 0%, 0 50%, 1 50%, 1 100%). Do we want to specify how the serialization should be carried out?

Copy link
Contributor Author

@jakearchibald jakearchibald Jun 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens with linear-gradient? If there's precedent there, same should happen here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linear-gradient(0deg, red 0% 10%, green, blue 90%); seems to become llinear-gradient(0deg, rgb(255, 0, 0) 0%, rgb(255, 0, 0) 10%, rgb(0, 128, 0), rgb(0, 0, 255) 90%);.
That said, not sure if I 100% agree on keeping it the same as linear-gradient:

  • There may be a lot more stops being supplied for the easing function in general
  • linear-gradient's function equivalent is really called once for paint vs. every frame of animation for this easing function.

So the two options of keeping both presentable & function forms, or calculating the function form every time seem... unattractive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's a little weird that syntax unfolds like that, but I think it still makes sense to do the same as gradient.


</section>

<h3 id="the-linear-easing-keyword">The linear easing keyword: ''linear''</h3>

The <dfn dfn-type=value for=easing-function>linear</dfn> keyword is equivalent to ''linear()''.

<h3 id=cubic-bezier-easing-functions oldids=cubic-bezier-timing-functions>Cubic
B&eacute;zier easing functions:
Expand Down