Skip to content

Commit 60de0c1

Browse files
committed
doxx
1 parent a661baf commit 60de0c1

File tree

3 files changed

+305
-25
lines changed

3 files changed

+305
-25
lines changed

README.md

Lines changed: 280 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
# keyframes.js
1+
# keyframes.js ![image](./assets/cube.png)
22

33
Create keyframe animations for **anything** in JavaScript; specify your keyframes in standards-complaint CSS.
44

5+
[demo 🌈](https://mkbabb.github.io/keyframes.js/)
6+
57
## Quick Start using `CSS`
68

7-
Add a function to be called on each frame:
9+
#### Create a new `CSSKeyframesAnimation` object:
810

911
```ts
10-
const transformCSSKeyframes = (t: number, vars) => {
11-
const { transform, backgroundColor } = vars;
12-
13-
box.style.transform = transform.toString();
14-
box.style.backgroundColor = backgroundColor.toString();
15-
};
12+
const anim = new CSSKeyframesAnimation({
13+
duration: 2000,
14+
iterationCount: Infinity,
15+
direction: "alternate",
16+
fillMode: "forwards",
17+
});
1618
```
1719

18-
Add keyframes to the animation:
20+
#### Specify your keyframes in CSS:
1921

2022
```css
2123
@keyframes mijn-keyframes {
@@ -31,36 +33,290 @@ Add keyframes to the animation:
3133
`;
3234
```
3335

36+
#### Add the keyframes to your animation, and add target elements to animate:
37+
38+
```ts
39+
const CSSKeyframes = /* the above string */ ...
40+
anim.fromCSSKeyframes(CSSKeyframes);
41+
anim.addTargets(document.getElementById("myElement"));
42+
```
43+
44+
#### Play ▶️
45+
3446
```ts
35-
const CSSKeyframes = ...
36-
anim.fromCSSKeyframes(CSSKeyframes, transformCSSKeyframes);
47+
anim.play();
48+
```
49+
50+
This will animate the above element by way of its style properties, as specified in the keyframes. This is only the default behaviour; you can get far more funky with it.
51+
52+
The above is plucked directly from the [`demo/simple`](demo/simple/App.vue) Vue file.
53+
54+
## Table of Contents
55+
56+
- [keyframes.js ](#keyframesjs-)
57+
- [Quick Start using `CSS`](#quick-start-using-css)
58+
- [Create a new `CSSKeyframesAnimation` object:](#create-a-new-csskeyframesanimation-object)
59+
- [Specify your keyframes in CSS:](#specify-your-keyframes-in-css)
60+
- [Add the keyframes to your animation, and add target elements to animate:](#add-the-keyframes-to-your-animation-and-add-target-elements-to-animate)
61+
- [Play ▶️](#play-️)
62+
- [Table of Contents](#table-of-contents)
63+
- [Installation](#installation)
64+
- [`Animation`](#animation)
65+
- [`AnimationOptions`:](#animationoptions)
66+
- [The transform function](#the-transform-function)
67+
- [The timing function](#the-timing-function)
68+
- [Step Functions](#step-functions)
69+
- [Bézier Curves](#bézier-curves)
70+
- [Graphing Bézier Curves](#graphing-bézier-curves)
71+
- [Just gimme the `t` value](#just-gimme-the-t-value)
72+
- [`TemplateAnimationFrame`](#templateanimationframe)
73+
- [Reification of a `TemplateAnimationFrame`](#reification-of-a-templateanimationframe)
74+
- [Variable Resolution](#variable-resolution)
75+
- [`CSSKeyframesAnimation`](#csskeyframesanimation)
76+
- [Parsing CSS Keyframes; `keyframes.ts`](#parsing-css-keyframes-keyframests)
77+
- [Units: `units.ts` \& `CSS Units`](#units-unitsts--css-units)
78+
- [Unit class hierarchy](#unit-class-hierarchy)
79+
- [Unit interpolation and resolution](#unit-interpolation-and-resolution)
80+
81+
## Installation
82+
83+
```bash
84+
npm install keyframes.ts
3785
```
3886

39-
## `Animation` and `CSSKeyFramesAnimation`
87+
Which will _mostly_ work both in and out of the browser. Anything that leverages the the `DOM`, of course, won't work outside of the browser (things like `getComputedStyle`, `document`, etc.).
4088

41-
## Groups of `Animation`
89+
## `Animation`
4290

43-
## Math
91+
The `Animation` object is the driver behind `CSSKeyFramesAnimation` and `AnimationGroup`.
4492

45-
The more interesting part of the library is the collection of various timing functions housed within
46-
[`easing.ts`](src/easing.ts), with utility math functions being defined in [`math.ts`](src/math.ts).
93+
Every `Animation` is composed of (at a high level):
4794

48-
### Bezier Curves
95+
- options: the options for the animation; `AnimationOptions`
96+
- a transform function: the function to interpolate between keyframes
97+
- a timing function: the function to ease the animation, which can also be set by the `AnimationOptions`
98+
- keyframes: the keyframes for the animation; `TemplateAnimationFrame`
4999

50-
For instance, `deCasteljau` is a dynamic programming implementation thereof, allowing for (semi) fast n-th degree Bezier curve calculations. This is used by every `bezier*` function herein.
100+
### [`AnimationOptions`](src/animation.ts#L139):
51101

52-
To create your own n-th degree Bezier timing function, you can use the aforesaid; to create the more common cubic Bezier curve, the appellative `cubicBezier` is provided: it's essentially identical in function to the similar CSS variant thereof:
102+
- duration: time in milliseconds of the entire animation
103+
- delay: time in milliseconds before the animation starts
104+
- iterationCount: number of times the animation should repeat
105+
- direction: direction of the animation (normal, reverse, alternate, alternate-reverse)
106+
- fillMode: how the animation should apply styles before and after it plays (none, forwards, backwards, both)
107+
- timing function: the timing function to use for easing, tweening, etc., the animation
108+
-
109+
110+
### The transform function
111+
112+
The type signature of the transform function is as follows:
113+
114+
```ts
115+
type TransformFunction<V extends Vars> = (t: number, v: V) => void;
116+
```
117+
118+
And it's called for each timestep `t` of the animation, where `t` is a number between 0 and the duration of the animation. The transform function is responsible for doing whatever you'd like to do with the variables `v` at each timestep `t`.
119+
120+
The variables `v` are the interpolated values at time `t`, given to you in almost exactly the same form as you originally specified them in the keyframes. Deeply nested objects are supported, as are just about anything else you can think of.
121+
122+
Every value therein is parsed as a CSS value unit, so you can specify things like `1px`, `1em`, `1%`, `1deg`, etc. The library will handle the conversion for you, though two interpolate between two different units, they must be of the same super type (e.g. `px` and `em` are both `length`s, so they can be interpolated; `px` and `deg` are not, so they cannot). See the `collapseNumericType` function within [`units.ts`](src/units.ts) for more information.
123+
124+
### The timing function
125+
126+
The timing, or easing, tweening, etc., function is responsible for determining how the animation progresses over time. The type signature of the timing function is as follows:
127+
128+
```ts
129+
type TimingFunction = (t: number) => number;
130+
```
131+
132+
Where `t` is a number between 0 and 1, and the return value is also a number between 0 and 1. The timing function is responsible for determining how the animation progresses over time, and can be anything from a simple linear function to a complex Bezier curve.
133+
134+
All CSS timing functions are supported, and are implemented in [`easing.ts`](src/easing.ts).
135+
136+
#### Step Functions
137+
138+
A special case and multi-parameter variant of a timing function, implemented as `steppedEase`, which takes (in addition to `t`) two parameters:
139+
140+
- the number of steps
141+
- the direction, or jump term, of the step
142+
143+
Valid jump terms are:
144+
145+
- `jump-none`: the step occurs at the start of the step, but the value is held until the end of the step
146+
- `jump-start` | `start`: the step occurs at the start of the step
147+
- `jump-end` | `end`: the step occurs at the end of the step
148+
- `jump-both` | `both`: the step occurs at the start and end of the step
149+
150+
#### Bézier Curves
151+
152+
Bézier curves are parametric curves defined by a set of control points.
153+
154+
The `cubicBezier` function implements the special cubic case of the more general Bézier curve, taking in control points for `x1`, `y1`, `x2`, and `y2`, and returning the x and y coordinates of the curve at time `t`.
53155

54156
```ts
55157
const [x, y] = cubicBezier(t / duration, 0.09, 0.91, 0.5, 1.5);
56158
```
57159

58-
> _`cubicBezier` returns both the x and y coordinates: typically one's only interested in the y._
160+
The general case of calculating a point along a Bézier curve at time `t`, specified at control points `x1, ..., xn`, `y1, ..., yn`, is performed using `deCasteljau`'s algorithm, implemented iteratively as simply the `deCasteljau` function.
161+
162+
Both of the above, along with other math utilities, are implemented in [`math.ts`](src/math.ts).
163+
164+
#### Graphing Bézier Curves
165+
166+
If you're interested in more Bézier visualizations, check out [this](https://www.desmos.com/calculator/tvivnkflzv) Desmos graph.
167+
168+
Or use any of the demos in the [`demo`](demo) folder, click on `timing-functions` and then `bezier`.
169+
170+
#### Just gimme the `t` value
171+
172+
OK ✨
173+
174+
`CSSBezier` is the function you're looking for. It's a high-order function that takes in the control points of the Bezier curve and returns a function that takes in a time `t` and returns the value of the Bezier curve at that time.
175+
176+
For example, CSS's `easeInBounce` is defined as
177+
178+
```ts
179+
function easeInBounce(t: number) {
180+
t = CSSBezier(0.09, 0.91, 0.5, 1.5)(t);
181+
return t;
182+
}
183+
```
184+
185+
### `TemplateAnimationFrame`
186+
187+
A `TemplateAnimationFrame` object, or template keyframe, is a keyframe that's not yet been resolved to a concrete keyframe. It's composed of:
188+
189+
- id: the unique id of the keyframe; autoincremented number
190+
- start: the start time of the keyframe
191+
- vars: the variables of the keyframe to be interpolated
192+
- transform: the transform function of the keyframe
193+
- timingFunction: the timing function of the keyframe
194+
195+
Keyframes can have unique transform and timing functions, but that's not typical: usually you'll specify one transform and timing function for the entire animation (once a transform function is specified, it's used for all keyframes, similarly for the timing function; no need to list it twice).
196+
197+
#### Reification of a `TemplateAnimationFrame`
198+
199+
A `TemplateAnimationFrame` is reified into a concrete keyframe by the following process:
200+
201+
- parse the start time: this can be input as a string, which can take on any valid CSS time format (e.g. `1s`, `100ms`, `1.5s`, `1.5ms`, etc.), or as a number, or as a percentage (e.g. `50%`).
202+
- All times are then normalized to a percentage of the total duration of the animation.
203+
- resolve the transform and timing functions if they're null: if they are, they're resolved to the default transform and timing functions specified in the `AnimationOptions`.
204+
205+
Once all of the `TemplateAnimationFrame` objects have been added to an `Animation`, they're further parsed into a concrete keyframe by the following process:
206+
207+
- sort the keyframes by their starting percentage
208+
- resolve the variables for each keyframe
209+
- resolve the keyframes' start and stop times
210+
- calculate the keyframes' duration
211+
212+
##### Variable Resolution
213+
214+
This is done so that every keyframe has the same set of variables, and so that the variables are resolved to their concrete values. Take the following example:
215+
216+
```ts
217+
const keyframeVars1 = {
218+
x: 0,
219+
y: 0,
220+
};
221+
const keyframeVars2 = {
222+
z: 0,
223+
};
224+
const keyframeVars3 = {
225+
x: 1,
226+
y: 1,
227+
z: 1,
228+
};
229+
```
230+
231+
Notice that `x` and `y` are defined in the first and third keyframes, but not in the second. We handle this by working through the keyframes backwards and seeking the most recent keyframe that has the variable defined.
232+
233+
If it's not defined in any previous keyframes, we set it to the default value of the variable (usually `0`).
234+
235+
All of this above nets you the ability to specify keyframes in a rather hap-hazard way (perhaps not such a good thing 😅). For example, the below is a valid set of keyframes:
236+
237+
```ts
238+
const duration = 1000;
239+
const keyframe1 = {
240+
start: "0s",
241+
vars: {
242+
x: 0,
243+
y: 0,
244+
},
245+
};
246+
247+
const keyframe2 = {
248+
start: "100%",
249+
vars: {
250+
x: 0,
251+
y: 1,
252+
},
253+
};
254+
255+
const keyframe3 = {
256+
start: "500ms",
257+
vars: {
258+
x: 1,
259+
},
260+
};
261+
```
262+
263+
## `CSSKeyframesAnimation`
264+
265+
An abstraction over the `Animation` object, the `CSSKeyframesAnimation` object is responsible for creating animations from CSS keyframes. This is done by parsing the CSS keyframes into a series of `TemplateAnimationFrame` objects, thereupon adding them to a base `Animation` object.
266+
267+
### Parsing CSS Keyframes; [`keyframes.ts`](src/parsing/keyframes.ts)
268+
269+
Most of the CSS spec. is supported, including:
270+
271+
- `from`, `to`, and percentages
272+
- time units (`s`, `ms`, etc.)
273+
- lengths (`px`, `em`, etc.)
274+
- angles (`deg`, `rad`, etc.)
275+
- colors (`#fff`, `rgb(255, 255, 255)`, `lab(100, 0, 0)`, `lightblue`, etc.)
276+
- transforms (`translateX(100%)`, `rotate(1turn)`, etc.)
277+
- variables (`var(--my-var)`)
278+
- resolved math expressions (`calc(100% - 10px)`)
279+
- Any `key: value` pair that can be parsed by the `CSS` parser, where value can be
280+
- any CSS value
281+
- any CSS function
282+
- any list of CSS values or functions
283+
- a limited subset of `JSON`-like objects, though the implemention of `JSON-CSS` is on the roadmap
284+
285+
The implemented parser currently leverages the [`parsimmon`](https://github.com/jneen/parsimmon) parser combinator library 🙂‍↔
286+
287+
### Units: [`units.ts`](src/units.ts) & [`CSS Units`](src/parsing/units.ts)
288+
289+
A great deal of care has gone into the parsing and resolving of units within the CSS spec. Herein, we cover the following unit types:
290+
291+
- `length`
292+
- `angle`
293+
- `time`
294+
- `resolution`
295+
- `percentage`
296+
- `color`
297+
298+
See the parser within the [`CSS Units file`](src/parsing/units.ts) for more information.
299+
300+
#### Unit class hierarchy
301+
302+
A `unit` value comes in three forms, specified in the general [`units.ts`](src/units.ts) file:
303+
304+
- `ValueUnit`: a value with a string unit and an array of super types
305+
- `FunctionValue`: a function with a string name and an array of `ValueUnit`s
306+
- `ValueArray`: an array of `ValueUnit`s
307+
308+
Each of these has defined a set of core functions:
309+
310+
- `toString()`: returns the string representation of the unit, e.g. `1px` or `translateX(100%)`
311+
- `valueOf()`: if the value is unit-less, returns the value; otherwise, returns the string variant
312+
- `lerp(t: number,
313+
other: FunctionValue<T> | ValueArray<T> | ValueUnit<T>,
314+
target?: HTMLElement,)`: interpolates between two units
59315

60-
I strongly recommend you use [this](https://cubic-bezier.com/) great website to play around within different curves visually.
316+
Note that any `ValueUnit` type variant can be interpolated between another; insofar as, a `ValeUnit` can be interpolated between a `FunctionValue` or `ValueArray`, and vice versa. The values thereof are aligned to the smallest array length of the two: the interpolation is then performed on each element of the array.
61317

62-
Finally, as another interactive Bezier demo, you can check out [this](https://www.desmos.com/calculator/tvivnkflzv) Desmos graph I've made. Pretty neat!
318+
#### Unit interpolation and resolution
63319

64-
### [`easing.ts`](src/easing.ts)
320+
Units that are of the same supertype can be interpolated between. For example, `px` and `em` are both `length`s, so they can be interpolated between. `px` and `deg` are not, so they cannot.
65321

66-
Additionally, a portion of [Robert Penner's](http://robertpenner.com/easing/) set of easing functions are implemented, though using a modified scheme which assumes the input `t` value is on the unit interval `[0, 1]`
322+
Supertypes also contain information about the realtive or absolute nature of the unit. For example, `px` is an absolute length, while `em` is a relative length. This information is used to resolve the units to a common supertype, which is then used to interpolate between the two units.

src/animation.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,27 @@ const getTimingFunction = (
175175
return timingFunction;
176176
};
177177

178+
179+
// const normalizeFrameStartTime = (frame: TemplateAnimationFrame<any>, duration: number) => {
180+
// let value = frame.start.value;
181+
182+
// if (frame.start.unit === "s") {
183+
// value = (value / duration) * 100;
184+
// }
185+
186+
// if (frame.start.unit === "ms") {
187+
// frame.start.unit = "%";
188+
189+
// const nextTime = i > 0 ? this.templateFrames[i - 1].start.value : 0;
190+
191+
// const msValue =
192+
// (nextTime * this.options.duration) / 100 + frame.start.value;
193+
// const percent = (msValue / this.options.duration) * 100;
194+
195+
// frame.start.value = percent;
196+
// }
197+
// }
198+
178199
let nextId = 0;
179200

180201
export class Animation<V extends Vars> {
@@ -243,6 +264,7 @@ export class Animation<V extends Vars> {
243264
for (let i = 0; i < this.templateFrames.length; i++) {
244265
const frame = this.templateFrames[i];
245266

267+
// Normalize start time to percentage
246268
if (frame.start.unit === "ms") {
247269
frame.start.unit = "%";
248270

src/easing.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const jumpTerms = [
7070
"jump-both",
7171
"start",
7272
"end",
73+
"both",
7374
] as const;
7475

7576
function jumpStart(t: number, steps: number): number {
@@ -90,7 +91,7 @@ function jumpNone(t: number, steps: number): number {
9091

9192
export function steppedEase(
9293
steps: number,
93-
jumpTerm: (typeof jumpTerms)[number] = "jump-start"
94+
jumpTerm: (typeof jumpTerms)[number] = "jump-start",
9495
) {
9596
switch (jumpTerm) {
9697
case "jump-none":
@@ -102,6 +103,7 @@ export function steppedEase(
102103
case "end":
103104
return (t: number) => jumpEnd(t, steps);
104105
case "jump-both":
106+
case "both":
105107
return (t: number) => jumpBoth(t, steps);
106108
}
107109
}

0 commit comments

Comments
 (0)