Skip to content

Commit

Permalink
[css-easing-2] Rewrite the linear() output algo to handle a single po…
Browse files Browse the repository at this point in the history
…int (and correctly handle a number of edge cases). #10580
  • Loading branch information
tabatkins committed Sep 10, 2024
1 parent a8f5f8c commit aaad9c7
Showing 1 changed file with 55 additions and 48 deletions.
103 changes: 55 additions & 48 deletions css-easing-2/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ Linear Easing Functions: ''linear'', ''linear()''</h3>
A <dfn export>linear easing function</dfn>
is an [=easing function=]
that interpolates linearly
between its [=control points=].
Each <dfn for="linear easing function">control point</dfn>
between its [=linear()/control points=].
Each <dfn for="linear()">control point</dfn>
is a pair of numbers,
associating an [=input progress value=]
to an [=output progress value=].
Expand All @@ -162,7 +162,7 @@ to an [=output progress value=].
alt="A linear curve used as an easing function.">
<figcaption>
''linear(0, .1 25%, .75 50%, 1)''<br>
The shape of the curve follows the [=control points=].<br>
The shape of the curve follows the [=linear()/control points=].<br>
Input progress values serve as <var>x</var> values of the curve,
whilst the <var>y</var> values are the output progress values.
</figcaption>
Expand All @@ -186,20 +186,20 @@ A [=linear easing function=] has the following syntax:
::
Specifies a [=linear easing function=].

Each comma-separated argument specifies one or two [=control points=],
Each comma-separated argument specifies one or two [=linear()/control points=],
with an [=input progress value=] equal to the specified <<percentage>>
(converted to a <<number>> between 0 and 1),
and an [=output progress value=] equal to the specified <<number>>.
When the argument has two <<percentage>>s,
it defines two [=control points=] in the specified order,
it defines two [=linear()/control points=] in the specified order,
one per <<percentage>>.

If an argument lacks a <<percentage>>,
its [=input progress value=] is initially empty,
but that is immediately corrected by [=linear() canonicalization=] after parsing.

<div algorithm>
To <dfn export lt="linear() canonicalization|canonicalize a linear()">canonicalize a linear()</dfn> function's [=control points=],
To <dfn export lt="linear() canonicalization|canonicalize a linear()">canonicalize a linear()</dfn> function's [=linear()/control points=],
perform the following:

1. If the first [=control point=] lacks an [=input progress value=],
Expand All @@ -214,10 +214,10 @@ A [=linear easing function=] has the following syntax:
of any preceding [=control point=].

4. If any [=control point=] still lacks an [=input progress value=],
then for each contiguous run of such [=control points=],
then for each contiguous run of such [=linear()/control points=],
set their [=input progress values=]
so that they are evenly spaced
between the preceding and following [=control points=]
between the preceding and following [=linear()/control points=]
with [=input progress values=].

After canonicalization,
Expand All @@ -229,7 +229,7 @@ A [=linear easing function=] has the following syntax:
was originally supplied,
so that information should be retained
in the [=CSS/internal representation=].
It does not rely on whether a pair of [=control points=]
It does not rely on whether a pair of [=linear()/control points=]
were specified as two percentages on a single argument
or as separate arguments.
</div>
Expand Down Expand Up @@ -269,8 +269,8 @@ Serializing</h4>

<div class=example>
When serialized,
[=control points=] originally specified with two [=input progress values=]
are turned into two separate [=control points=],
[=linear()/control points=] originally specified with two [=input progress values=]
are turned into two separate [=linear()/control points=],
and the [=input progress values=] are in strictly ascending order.
For example:

Expand All @@ -283,48 +283,55 @@ Serializing</h4>
<h4 id=linear-easing-function-output>
Output</h4>

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

To <dfn export>calculate linear easing 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. Let |points| be |linearEasingFunction|'s [=linear easing function/points=].

1. Let |pointAIndex| be index of the last [=list/item=] in |points|
with an [=linear easing point/input=] less than or equal to |inputProgress|,
or 0 if there is no match.

1. If |pointAIndex| is equal to |points| [=list/size=] minus 1,
decrement |pointAIndex| by 1.

Note: This ensures we have a "next" [=linear easing point|point=] to compare to.

1. Let |pointA| be |points|[pointAIndex].

1. Let |pointB| be |points|[pointAIndex + 1].

1. If |pointA|'s [=linear easing point/input=] is equal to |pointB|'s [=linear easing point/input=],
return |pointB|'s [=linear easing point/output=].

1. Let |progressFromPointA| be |inputProgress| minus |pointA|'s [=linear easing point/input=].

1. Let |pointInputRange| be |pointB|'s [=linear easing point/input=] minus |pointA|'s [=linear easing point/input=].

1. Let |progressBetweenPoints| be |progressFromPointA| divided by |pointInputRange|.

1. Let |pointOutputRange| be |pointB|'s [=linear easing point/output=] minus |pointA|'s [=linear easing point/output=].

1. Let |outputFromLastPoint| be |progressBetweenPoints| multiplied by |pointOutputRange|.
<div algorithm>

1. Return |pointA|'s [=linear easing point/output=] plus |outputFromLastPoint|.
To <dfn export>calculate linear easing output progress</dfn>
for a given [=linear easing function=] |func|,
and an [=input progress value=] |inputProgress|,
perform the following.
It returns an [=output progress value=].

1. Let |points| be |func|'s [=linear()/control points=].

2. If |points| holds only a single item,
return the [=output progress value=]
of that item.

3. If at least one of |points| has an [=input progress value=]
matching |inputProgress|,
return the largest [=output progress value=] from among them.

4. Otherwise, find two [=linear()/control points=],
|A| and |B|,
which will be used for interpolation:

1. If |inputProgress| is smaller
than any [=input progress value=] in |points|,
let |A| and |B| be the first two items in |points|.
If |A| and |B| have the same [=input progress value=],
return |A|'s [=output progress value=].

2. Otherwise, if |inputProgress| is larger
than any [=input progress value=] in |points|,
let |A| and |B| be the last two items in |points|.
If |A| and |B| have the same [=input progress value=],
return |B|'s [=output progress value=].

3. Otherwise, let |A| be the [=linear()/control point=]
with the largest [=output progress value=]
whose [=input progress value=] is smaller than |inputProgress|,
and let |B| be the [=linear()/control point=]
with the smallest [=output progress value=]
whose [=input progress value=] is larger than |inputProgress|.

5. Linearly interpolate (or extrapolate) |inputProgress|
along the line defined by |A| and |B|,
and return the result.
</div>

<wpt>
linear-timing-functions-output.html
</wpt>
</section>

<h4 id=linear-easing-function-examples>
Examples</h4>
Expand Down

0 comments on commit aaad9c7

Please sign in to comment.