diff --git a/css-easing-2/Overview.bs b/css-easing-2/Overview.bs index d89a762e631..de246ff9c30 100644 --- a/css-easing-2/Overview.bs +++ b/css-easing-2/Overview.bs @@ -7,10 +7,13 @@ Level: 2 Group: csswg ED: https://drafts.csswg.org/css-easing/ TR: https://www.w3.org/TR/css-easing-2/ +Editor: Jake Archibald, Google, jakearchibald@google.com, w3cid 76394 Editor: Brian Birtles, Mozilla https://www.mozilla.org/, bbirtles@mozilla.com, w3cid 43194 Editor: Dean Jackson, Apple Inc https://www.apple.com/, dino@apple.com, w3cid 42080 Editor: Matt Rakow, Microsoft, w3cid 62267 Former Editor: Shane Stephens, Google, shans@google.com, w3cid 47691 +Markup Shorthands: markdown yes +Indent: 2 Abstract: This CSS module describes a way for authors to define a transformation that controls the rate of change of some value. @@ -38,6 +41,14 @@ Repository: w3c/csswg-drafts } + + Introduction {#introduction} ============================ @@ -61,7 +72,7 @@ input progress value and producing a corresponding transformed output progress value.
- Example of an easing function that produces an ease-in effect.
Example of an easing function that produces an ease-in effect.
@@ -118,20 +129,323 @@ The syntax for specifying an [=easing function=] is as follows:
<easing-function> = ''linear'' | + ''linear()'' | <> | <>
+## The linear easing function: ''linear()'' ## {#the-linear-easing-function} + +A linear easing function +is an [=easing function=] +that interpolates linearly +between its [=linear easing function/points=]. + +A [=linear easing function=] has points, +a [=/list=] of [=linear easing points=]. +Initially a new empty [=/list=]. + +A linear easing point +is a [=/struct=] +that has: + +
+ +: input +:: A number or null + + Note: This is only null during [=create a linear easing function=]. + +: output +:: A number + +
+ +### Syntax ### {#linear-easing-function-syntax} + +A [=linear easing function=] has the following syntax: + +linear(<>) + +
+  <linear-stop-list> = [ <> ]#
+  <linear-stop> = <> && <>?
+  <linear-stop-length> = <>{1,2}
+
+ +''linear()'' is parsed into a [=linear easing function=] +by calling [=create a linear easing function=], +passing in its <> as a [=/list=] of <>s. + +### Parsing ### {#linear-easing-function-parsing} + +
+ +To create a linear easing function +given a [=/list=] of <>s |stopList|, +perform the following. +It returns a [=linear easing function=] or failure. + +1. Let |function| be a new [=linear easing function=]. + +1. Let |largestInput| be negative infinity. + +1. If there are less than two [=list/items=] in |stopList|, then return failure. + +1. [=list/For each=] |stop| in |stopList|: + + 1. Let |point| be a new [=linear easing point=] + with its [=linear easing point/output=] set to |stop|'s <> as a number. + + 1. [=list/Append=] |point| to |function|'s [=linear easing function/points=]. + + 1. If |stop| has a <>, then: + + 1. Set |point|'s [=linear easing point/input=] to whichever is greater: + |stop|'s <>'s first <> as a number, + or |largestInput|. + + 1. Set |largestInput| to |point|'s [=linear easing point/input=]. + + 1. If |stop|'s <> has a second <>, then: + + 1. Let |extraPoint| be a new [=linear easing point=] + with its [=linear easing point/output=] set to |stop|'s <> as a number. + + 1. [=list/Append=] |extraPoint| to |function|'s [=linear easing function/points=]. + + 1. Set |extraPoint|'s [=linear easing point/input=] to whichever is greater: + |stop|'s <>'s second <> 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|, then: + + 1. Set |point|'s [=linear easing point/input=] to 0. + + 1. Set |largestInput| to 0. + + 1. Otherwise, if |stop| is the last [=list/item=] in |stopList|, + then set |point|'s [=linear easing point/input=] to whichever is greater: + 1 or |largestInput|. + +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 closest previous and next [=linear easing function/points=] + that have a non-null [=linear easing point/input=]. + +1. Return |function|. + +
+ +### Serializing ### {#linear-easing-function-serializing} + +
+ The serialization of ''linear()'' includes input values for each point, + and input values are never less than the input of the previous point. + + For example: + + - ''linear(0, 0.25, 1)'' serializes as ''linear(0 0%, 0.25 50%, 1 100%)'' + - ''linear(0 20%, 0.5 10%, 1)'' serializes as ''linear(0 20%, 0.5 20%, 1 100%)'' + - ''linear(0, 0.25 25% 75%, 1)'' serializes as ''linear(0 0%, 0.25 25%, 0.25 75%, 1 100%)'' +
+ +
+ +To get a [=linear easing function=]'s (|linearEasingFunction|) serialized computed value, +perform the following. +It returns a [=string=]. + +1. Let |output| be "`linear(`". -

The linear easing function: ''linear''

+1. [=list/For each=] |point| in |linearEasingFunction|'s [=linear easing function/points=]: -The linear easing -function is an identity function + 1. If |point| is not the first [=list/item=] of |linearEasingFunction|'s [=linear easing function/points=], + append ", " to |output|. + + 1. Append the computed value of |point|'s [=linear easing point/output=], + as a <>, + to |output|. + + 1. Append " " to |output|. + + 1. Append the computed value of |point|'s [=linear easing point/input=], + as a <>, + to |output|. + +1. Append "`)`" to |output|. + +1. Return |output|. + +
+ +### Output of a linear easing function ### {#linear-easing-function-output} + +
+ +To calculate linear easing output progress +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|. + +1. Return |pointA|'s [=linear easing point/output=] plus |outputFromLastPoint|. + +
+ +### Examples ### {#linear-easing-function-examples} + +
+ ''linear()'' allows the definition of easing functions that interpolate linearly between a set of points. + + For example, ''linear(0, 0.25, 1)'' produces an easing function + that moves linearly from 0, to 0.25, then to 1: + +
+ linear(0, 0.25, 1) plotted on a graph +
+
+
+ By default, values are spread evenly between entries that don't have an explicit "input". + Input values can be provided using a <>. + + For example, ''linear(0, 0.25 75%, 1)'' produces the following easing function, + which spends 75% of the time transitioning from ''0'' to ''.25'', + then the last 25% transitioning from ''.25'' to ''1'': + +
+ linear(0, 0.25 75%, 1) plotted on a graph.
+        The graph has three points.
+        The first is at 0,0.
+        The second is at 0.75,0.25.
+        The third is at 1,1. +
+
+
+ If two input values are provided for a single output, + it results in two points with the same output. + + For example, ''linear(0, 0.25 25% 75%, 1)'' + is equivalent to ''linear(0, 0.25 25%, 0.25 75%, 1)'', + producing the following easing function: + +
+ linear(0, 0.25 75%, 1) plotted on a graph.
+        The graph has four points.
+        The first is at 0,0.
+        The second is at 0.25,0.25.
+        The third is at 0.75,0.25.
+        The forth is at 1,1. +
+
+
+ If the input is outside the range provided, + the trajectory of the nearest two points is continued. + + For example, here are the implicit values from the previous function: + +
+ linear(0, 0.25 75%, 1) plotted on a graph.
+        The graph has four points.
+        The first is at 0,0.
+        The second is at 0.25,0.25.
+        The third is at 0.75,0.25.
+        The forth is at 1,1.
+        The ends of the graph are extended at the angle of the nearest two lines. +
+
+
+ A typical use of ''linear()'' is to provide many points to create the illusion of a curve. + + For example, here's how ''linear()'' could be used to create a reusable "bounce" easing function: + + ```css + :root { + --bounce: linear( + /* Start to 1st bounce */ + 0, 0.063, 0.25, 0.563, 1 36.4%, + /* 1st to 2nd bounce */ + 0.812, 0.75, 0.813, 1 72.7%, + /* 2nd to 3rd bounce */ + 0.953, 0.938, 0.953, 1 90.9%, + /* 3rd bounce to end */ + 0.984, 1 100% 100% + ); + } + + .example { + animation-timing-function: var(--bounce); + } + ``` + + The definition ends `1 100% 100%` to create two final points, + so inputs greater than 1 always output 1. + +
+ The graph of a rough bounce easing. +
+ + More points could be used to create a smoother result, + which may be needed for slower animations. +
+ +

The linear easing keyword: ''linear''

+ +The linear keyword +produces a [=linear easing function=] +with two [=linear easing function/points=]: + +1. : [=linear easing point/input=] + :: 0 + : [=linear easing point/output=] + :: 0 + +1. : [=linear easing point/input=] + :: 1 + : [=linear easing point/output=] + :: 1 + +Note: This results in an identity function, meaning that its [=output progress value=] is equal to the [=input progress value=] for all inputs. -The syntax for the [=linear easing function=] is simply the -linear keyword. - +Note: Although this produces a [=linear easing function=], +uses of the keyword ''linear'' always serialize as-is, to ''linear''. +Whereas the function equivalent ''linear(0, 1)'' will serialize to ''linear(0 0%, 1 100%)''. +These rules are in [Serialization](#serialization).

Cubic Bézier easing functions: @@ -147,7 +461,7 @@ The x coordinates of P1 and P2 are restricted to the range [0, 1].
- A cubic Bezier curve used as an easing function.
A cubic Bézier curve used as an easing function.
@@ -192,7 +506,7 @@ The meaning of each value is as follows: The keyword values listed above are illustrated below.
- The easing functions produced by keyword values.
The easing functions produced by each of the cubic Bézier easing @@ -264,7 +578,7 @@ The meaning of each value is as follows: :: Computes to ''steps(1, end)''
- Example step easing keywords.
Example step easing function keyword values. @@ -303,7 +617,7 @@ The meaning of each value is as follows: These values are illustrated below:
- Example step easing functions.
Example step easing functions. @@ -426,8 +740,8 @@ defined in [[CSSOM]] with the following additional requirements: * The keyword values ''ease'', ''linear'', ''ease-in'', ''ease-out'', and ''ease-in-out'' are serialized as-is, that is, they are - not converted to the equivalent ''cubic-bezier()'' - function before serializing. + not converted to the equivalent ''cubic-bezier()'' or + ''linear()'' function before serializing. * Step easing functions, whether they are specified using the ''steps()'' function or either of the ''step-start'' or ''step-end'' @@ -439,6 +753,9 @@ defined in [[CSSOM]] with the following additional requirements: 2. Otherwise, serialize as steps(<integer>, <step-position>). +* A [=linear easing function=] created via ''linear()'' + is serialized by getting its [=linear easing function/serialized computed value=]. + Privacy and Security Considerations {#priv-sec} =================================== diff --git a/css-easing-2/cubic-bezier-easing-curve.svg b/css-easing-2/cubic-bezier-easing-curve.svg index bdbc09da40e..f3a78cfe752 100644 --- a/css-easing-2/cubic-bezier-easing-curve.svg +++ b/css-easing-2/cubic-bezier-easing-curve.svg @@ -10,54 +10,63 @@ font-family: sans-serif; } + svg { + --foreground: black; + --background: white; + --grey: grey; + --blue: blue; + } + + @media (prefers-color-scheme: dark) { + svg { + --foreground: #ddd; + --background: black; + --grey: grey; + --blue: #6969ff; + } + } + /* * Line work */ .axis, .tickMark { - stroke: black; + stroke: var(--foreground); stroke-width: 1.5; fill: none; stroke-linecap: square; } .boundaryLine { - stroke: grey; + stroke: var(--grey); stroke-width: 1.5; stroke-dasharray: 3 4; fill: none; } .curve { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 7; stroke-linecap: round; } .controlPoint { - fill: white; - stroke: black; + fill: var(--background); + stroke: var(--foreground); stroke-width: 3; } .handleLine { fill: none; - stroke: black; + stroke: var(--foreground); stroke-width: 2; } - /* - * Background - */ - .graphBackground { - stroke: none; - fill: white; - } - /* * Text labels */ .axisLabel { text-anchor: middle; + fill: var(--foreground); } .axisValue { - fill: grey; + fill: var(--grey); } .axisValue.vert { text-anchor: end; @@ -66,15 +75,13 @@ text-anchor: middle; } .pointLabel { - fill: black; + fill: var(--foreground); font-size: 25px; } - - diff --git a/css-easing-2/curve-keywords.svg b/css-easing-2/curve-keywords.svg index 5bac0c9ea9c..168840e8e4c 100644 --- a/css-easing-2/curve-keywords.svg +++ b/css-easing-2/curve-keywords.svg @@ -10,51 +10,61 @@ font-family: sans-serif; } + svg { + --foreground: black; + --background: white; + --grey: grey; + --blue: blue; + --red: red; + } + + @media (prefers-color-scheme: dark) { + svg { + --foreground: #ddd; + --background: black; + --grey: grey; + --blue: #6969ff; + --red: #ff6767; + } + } + /* * Line work */ .axis, .tickMark { - stroke: black; + stroke: var(--foreground); stroke-width: 1.2; fill: none; stroke-linecap: square; } .boundaryLine { - stroke: grey; + stroke: var(--grey); stroke-width: 1; stroke-dasharray: 2 3; fill: none; } .curve { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 3; stroke-linecap: round; } .controlPoint { - fill: white; - stroke: black; + fill: var(--background); + stroke: var(--foreground); stroke-width: 1; } .handleLine { fill: none; - stroke: black; + stroke: var(--foreground); stroke-width: 2; } - /* - * Background - */ - .graphBackground { - stroke: none; - fill: white; - } - /* * Text labels */ .axisValue { - fill: grey; + fill: var(--grey); } .axisValue.vert { text-anchor: end; @@ -63,18 +73,17 @@ text-anchor: middle; } .pointLabel { - fill: black; + fill: var(--foreground); font-size: 11px; } .keyword { font-size: 13px; text-anchor: middle; + fill: var(--foreground); } - - diff --git a/css-easing-2/easing-function-example.svg b/css-easing-2/easing-function-example.svg index 01e9b3820a1..56fff93a216 100644 --- a/css-easing-2/easing-function-example.svg +++ b/css-easing-2/easing-function-example.svg @@ -10,23 +10,41 @@ font-family: sans-serif; } + svg { + --foreground: black; + --background: white; + --grey: grey; + --blue: blue; + --red: red; + } + + @media (prefers-color-scheme: dark) { + svg { + --foreground: #ddd; + --background: black; + --grey: grey; + --blue: #6969ff; + --red: #ff6767; + } + } + /* * Line work */ .axis, .tickMark { - stroke: black; + stroke: var(--foreground); stroke-width: 1.5; fill: none; stroke-linecap: square; } .boundaryLine { - stroke: grey; + stroke: var(--grey); stroke-width: 1.5; stroke-dasharray: 3 4; fill: none; } .intersectionLine { - stroke: red; + stroke: var(--red); stroke-width: 5; stroke-dasharray: 4 16; stroke-linecap: round; @@ -34,7 +52,7 @@ } .curve { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 7; stroke-linecap: round; } @@ -44,12 +62,13 @@ */ .axisLabel { text-anchor: middle; + fill: var(--foreground); } .axisValue { - fill: grey; + fill: var(--grey); } .intersectVal { - fill: red; + fill: var(--red); font-size: 32px; } .axisValue.vert, .intersectVal.vert { @@ -62,8 +81,6 @@ - - diff --git a/css-easing-2/linear-bounce-example.svg b/css-easing-2/linear-bounce-example.svg new file mode 100644 index 00000000000..3c7285ef0b7 --- /dev/null +++ b/css-easing-2/linear-bounce-example.svg @@ -0,0 +1,122 @@ + + + Easing function example + + + + + + + + + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Output progress + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Input progress + + 0 + + + + + diff --git a/css-easing-2/linear-with-double-input-example-continued.svg b/css-easing-2/linear-with-double-input-example-continued.svg new file mode 100644 index 00000000000..d482fd585b0 --- /dev/null +++ b/css-easing-2/linear-with-double-input-example-continued.svg @@ -0,0 +1,124 @@ + + + Easing function example + + + + + + + + + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Output progress + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Input progress + + 0 + + + + + diff --git a/css-easing-2/linear-with-double-input-example.svg b/css-easing-2/linear-with-double-input-example.svg new file mode 100644 index 00000000000..b987cbc8a11 --- /dev/null +++ b/css-easing-2/linear-with-double-input-example.svg @@ -0,0 +1,113 @@ + + + Easing function example + + + + + + + + + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Output progress + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Input progress + + 0 + + + + diff --git a/css-easing-2/linear-with-input-example.svg b/css-easing-2/linear-with-input-example.svg new file mode 100644 index 00000000000..b71a9c215ea --- /dev/null +++ b/css-easing-2/linear-with-input-example.svg @@ -0,0 +1,112 @@ + + + Easing function example + + + + + + + + + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Output progress + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Input progress + + 0 + + + + diff --git a/css-easing-2/simple-linear-example.svg b/css-easing-2/simple-linear-example.svg new file mode 100644 index 00000000000..07a984b7480 --- /dev/null +++ b/css-easing-2/simple-linear-example.svg @@ -0,0 +1,112 @@ + + + Easing function example + + + + + + + + + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Output progress + + + + + + + 0.25 + 0.5 + 0.75 + 1 + Input progress + + 0 + + + + diff --git a/css-easing-2/step-easing-func-examples.svg b/css-easing-2/step-easing-func-examples.svg index 236ed9bb184..7aedc5bdebd 100644 --- a/css-easing-2/step-easing-func-examples.svg +++ b/css-easing-2/step-easing-func-examples.svg @@ -10,46 +10,56 @@ font-family: sans-serif; } + svg { + --foreground: black; + --background: white; + --grey: grey; + --blue: blue; + --red: red; + } + + @media (prefers-color-scheme: dark) { + svg { + --foreground: #ddd; + --background: black; + --grey: grey; + --blue: #6969ff; + --red: #ff6767; + } + } + /* * Line work */ .axis, .tickMark { - stroke: black; + stroke: var(--foreground); stroke-width: 1.2; fill: none; stroke-linecap: square; } .curve { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 3; stroke-linecap: round; } .dotted { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 1.5; stroke-linecap: round; stroke-dasharray: 2 6; } .filledEndPoint { - fill: blue; + fill: var(--blue); stroke: none; } .openEndPoint { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 1.5; } - /* - * Background - */ - .graphBackground { - stroke: none; - fill: white; - } - /* * Masking */ @@ -67,7 +77,7 @@ * Text labels */ .axisValue { - fill: grey; + fill: var(--grey); } .axisValue.vert { text-anchor: end; @@ -78,12 +88,10 @@ .paramLabel { font-size: 13px; text-anchor: middle; + fill: var(--foreground); } - - - diff --git a/css-easing-2/step-easing-keyword-examples.svg b/css-easing-2/step-easing-keyword-examples.svg index 2cd471a906a..412732123a0 100644 --- a/css-easing-2/step-easing-keyword-examples.svg +++ b/css-easing-2/step-easing-keyword-examples.svg @@ -10,46 +10,56 @@ font-family: sans-serif; } + svg { + --foreground: black; + --background: white; + --grey: grey; + --blue: blue; + --red: red; + } + + @media (prefers-color-scheme: dark) { + svg { + --foreground: #ddd; + --background: black; + --grey: grey; + --blue: #6969ff; + --red: #ff6767; + } + } + /* * Line work */ .axis, .tickMark { - stroke: black; + stroke: var(--foreground); stroke-width: 1.2; fill: none; stroke-linecap: square; } .curve { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 3; stroke-linecap: round; } .dotted { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 1.5; stroke-linecap: round; stroke-dasharray: 2 6; } .filledEndPoint { - fill: blue; + fill: var(--blue); stroke: none; } .openEndPoint { fill: none; - stroke: blue; + stroke: var(--blue); stroke-width: 1.5; } - /* - * Background - */ - .graphBackground { - stroke: none; - fill: white; - } - /* * Masking */ @@ -67,7 +77,7 @@ * Text labels */ .axisValue { - fill: grey; + fill: var(--grey); } .axisValue.vert { text-anchor: end; @@ -78,12 +88,10 @@ .paramLabel { font-size: 13px; text-anchor: middle; + fill: var(--foreground); } - - -