@maravilla-labs/frames 0.1.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -66
- package/dist/index.d.ts +51 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +177 -14
package/README.md
CHANGED
|
@@ -1,92 +1,175 @@
|
|
|
1
1
|
# @maravilla-labs/frames
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Declare a timeline of animations and video playback for a page. The same
|
|
4
|
+
declaration works two ways:
|
|
5
5
|
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
paused and the Rust renderer steps `currentTime` frame-by-frame via
|
|
10
|
-
`window.__mvFrames.applyState(t)`, producing a deterministic MP4.
|
|
6
|
+
- In a normal browser, each item plays at its scheduled time on its own.
|
|
7
|
+
- When the page is rendered to video by the Maravilla runtime, the
|
|
8
|
+
timeline is stepped frame by frame to produce a deterministic clip.
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
Zero dependencies. Bring your own animation library if you want one;
|
|
11
|
+
nothing is required.
|
|
14
12
|
|
|
15
|
-
##
|
|
13
|
+
## Install
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
```sh
|
|
16
|
+
pnpm add @maravilla-labs/frames
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { defineTimeline } from "@maravilla-labs/frames";
|
|
23
|
+
|
|
24
|
+
defineTimeline({
|
|
25
|
+
duration: 5000, // total timeline length, ms
|
|
26
|
+
|
|
27
|
+
instructions: [
|
|
28
|
+
// Title fades in from t=0 over the first 500 ms.
|
|
29
|
+
{
|
|
30
|
+
at: 0,
|
|
31
|
+
duration: 500,
|
|
32
|
+
selector: "h1",
|
|
33
|
+
props: [{ opacity: 0 }, { opacity: 1 }],
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Subtitle slides up, starting 300 ms after the title begins.
|
|
37
|
+
{
|
|
38
|
+
at: 300,
|
|
39
|
+
duration: 600,
|
|
40
|
+
selector: ".subtitle",
|
|
41
|
+
props: [
|
|
42
|
+
{ opacity: 0, transform: "translateY(20px)" },
|
|
43
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// A clip plays from t=2000 ms until t=5000 ms (3 s of video).
|
|
48
|
+
{
|
|
49
|
+
kind: "video",
|
|
50
|
+
at: 2000,
|
|
51
|
+
duration: 3000,
|
|
52
|
+
selector: "video.intro",
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Call `defineTimeline` once, after the elements referenced by every
|
|
59
|
+
`selector` exist in the DOM.
|
|
60
|
+
|
|
61
|
+
## How `at` and `duration` interact
|
|
62
|
+
|
|
63
|
+
| Field | Meaning |
|
|
64
|
+
| ---------- | ------------------------------------------------------------------ |
|
|
65
|
+
| `at` | When on the timeline this item starts, in ms |
|
|
66
|
+
| `duration` | How long this item runs from `at`, in ms. Omit for sensible default |
|
|
67
|
+
|
|
68
|
+
Two animations with the same `at` start at the same time. Two
|
|
69
|
+
animations with different `at` values start at different times. A
|
|
70
|
+
`duration` of `500` ms means the animation reaches its end keyframe
|
|
71
|
+
500 ms after it started, regardless of how long the total timeline is.
|
|
72
|
+
|
|
73
|
+
If you omit `duration` on an animation, it runs from `at` until the
|
|
74
|
+
end of the timeline. If you omit `duration` on a video, it plays for
|
|
75
|
+
its natural length.
|
|
76
|
+
|
|
77
|
+
## Animations
|
|
78
|
+
|
|
79
|
+
Each animation instruction maps to a single
|
|
18
80
|
[`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation)
|
|
19
|
-
created
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
property — no implicit clock involved.
|
|
81
|
+
created via `Element.animate(keyframes, { duration, fill: "both" })`.
|
|
82
|
+
Anything the Web Animations API supports as a keyframe is valid in
|
|
83
|
+
`props`:
|
|
23
84
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
85
|
+
```ts
|
|
86
|
+
{
|
|
87
|
+
at: 1000,
|
|
88
|
+
duration: 800,
|
|
89
|
+
selector: ".card",
|
|
90
|
+
props: [
|
|
91
|
+
{ transform: "scale(0.9)", opacity: 0 },
|
|
92
|
+
{ transform: "scale(1)", opacity: 1 },
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
```
|
|
27
96
|
|
|
28
|
-
-
|
|
29
|
-
state via `applyState(t)`.
|
|
30
|
-
- flag unset ⇒ animations play normally, so humans visiting the page
|
|
31
|
-
can preview what the renderer will capture.
|
|
97
|
+
## CSS-class animations (Animate.css and friends)
|
|
32
98
|
|
|
33
|
-
|
|
99
|
+
Use `kind: "css"` to schedule an animation defined as CSS `@keyframes`
|
|
100
|
+
and triggered by a class — for example anything from
|
|
101
|
+
[Animate.css](https://animate.style/):
|
|
34
102
|
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
defineTimeline({
|
|
44
|
-
duration: 2000,
|
|
45
|
-
instructions: [
|
|
46
|
-
{ at: 0, selector: "#title", props: [{ opacity: 0 }, { opacity: 1 }] },
|
|
47
|
-
],
|
|
48
|
-
});
|
|
49
|
-
</script>
|
|
50
|
-
</body>
|
|
51
|
-
</html>
|
|
103
|
+
```ts
|
|
104
|
+
{
|
|
105
|
+
kind: "css",
|
|
106
|
+
at: 1400,
|
|
107
|
+
duration: 1000, // optional — defaults to the longest keyframes duration on the element
|
|
108
|
+
selector: ".badge",
|
|
109
|
+
classes: ["animate__animated", "animate__bounceInDown"],
|
|
110
|
+
}
|
|
52
111
|
```
|
|
53
112
|
|
|
54
|
-
|
|
113
|
+
The helper adds the classes, captures the resulting CSS animation as
|
|
114
|
+
a WAAPI `Animation` object via `Element.getAnimations()`, and drives
|
|
115
|
+
its `currentTime` exactly like every other slot. The visual is the
|
|
116
|
+
class's native CSS animation; the timing is yours to control.
|
|
117
|
+
|
|
118
|
+
The stylesheet that defines the keyframes must be loaded before
|
|
119
|
+
`defineTimeline` runs. The simplest pattern is to `await` the page
|
|
120
|
+
`load` event first inside a `<script type="module">`:
|
|
55
121
|
|
|
56
|
-
```
|
|
57
|
-
await
|
|
122
|
+
```ts
|
|
123
|
+
await new Promise(r => addEventListener("load", r, { once: true }));
|
|
124
|
+
defineTimeline({ /* ... */ });
|
|
58
125
|
```
|
|
59
126
|
|
|
60
|
-
|
|
61
|
-
|
|
127
|
+
This works for any library that exposes its motion as CSS classes
|
|
128
|
+
plus shipped `@keyframes` — Animate.css, Magic, your own custom
|
|
129
|
+
keyframes — not just Animate.css specifically.
|
|
62
130
|
|
|
63
|
-
|
|
131
|
+
When you pass an explicit `duration`, the helper writes
|
|
132
|
+
`animation-duration` inline with `!important` so it beats any
|
|
133
|
+
`@media (prefers-reduced-motion: reduce)` rule the library ships with
|
|
134
|
+
(Animate.css collapses to 1 ms otherwise). You opted into this
|
|
135
|
+
timeline; the helper assumes you mean it.
|
|
64
136
|
|
|
65
|
-
|
|
137
|
+
## Video
|
|
66
138
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
referenced by every `selector` exist in the DOM.
|
|
139
|
+
Use `kind: "video"` to schedule a `<video>` element on the timeline.
|
|
140
|
+
Optionally seek inside the source file with `seek`:
|
|
70
141
|
|
|
71
142
|
```ts
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
instructions: FrameInstr[];
|
|
80
|
-
};
|
|
143
|
+
{
|
|
144
|
+
kind: "video",
|
|
145
|
+
at: 4000, // start playing at t=4 s on the timeline
|
|
146
|
+
duration: 6000, // play for 6 s
|
|
147
|
+
seek: 12000, // start 12 s into the source file
|
|
148
|
+
selector: "video#hero",
|
|
149
|
+
}
|
|
81
150
|
```
|
|
82
151
|
|
|
83
|
-
|
|
84
|
-
`
|
|
152
|
+
The `<video>` element itself stays as you'd write it in HTML — set
|
|
153
|
+
`src`, `preload="auto"`, and `muted` so the browser autoplay policy
|
|
154
|
+
doesn't block it.
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<video class="intro" src="/clips/intro.mp4" preload="auto" muted playsinline></video>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If the same composition will be rendered to a final video by the
|
|
161
|
+
Maravilla runtime, the audio track of the embedded `<video>` is not
|
|
162
|
+
captured (browsers don't expose decoded audio to the rendering API).
|
|
163
|
+
Use the `audio` option on `FRAMES.render(...)` to mix a soundtrack
|
|
164
|
+
into the final output instead.
|
|
85
165
|
|
|
86
166
|
## Bring your own animation library
|
|
87
167
|
|
|
88
|
-
Any library that exposes an explicit time value works
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
168
|
+
Any library that exposes an explicit time value works alongside this
|
|
169
|
+
helper. Drive its time cursor inside the same applyState-equivalent
|
|
170
|
+
hook and the renderer samples it deterministically. GSAP, Lottie, and
|
|
171
|
+
Three.js all fit this shape.
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @maravilla-labs/frames — declare
|
|
3
|
-
*
|
|
2
|
+
* @maravilla-labs/frames — declare a timeline of animations, video, and
|
|
3
|
+
* CSS-class-driven animations (Animate.css, etc.) for a page. The same
|
|
4
|
+
* declaration works two ways:
|
|
4
5
|
*
|
|
5
|
-
* In a normal browser
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* them via `window.__mvFrames.applyState(t)` for deterministic output.
|
|
6
|
+
* - In a normal browser, each item plays at its scheduled `at` time.
|
|
7
|
+
* - Inside the Maravilla renderer, the timeline is stepped frame by
|
|
8
|
+
* frame to produce deterministic video output.
|
|
9
9
|
*/
|
|
10
|
-
export type
|
|
10
|
+
export type AnimationInstr = {
|
|
11
|
+
/** Optional discriminator. Defaults to `"animation"`. */
|
|
12
|
+
kind?: "animation";
|
|
13
|
+
/** When on the timeline this animation starts, in milliseconds. */
|
|
11
14
|
at: number;
|
|
15
|
+
/** How long the animation runs from `at`, in ms. Defaults to (schema.duration - at). */
|
|
16
|
+
duration?: number;
|
|
17
|
+
/** CSS selector for the element being animated. */
|
|
12
18
|
selector: string;
|
|
19
|
+
/** Web Animations API keyframes, e.g. `[{ opacity: 0 }, { opacity: 1 }]`. */
|
|
13
20
|
props: Keyframe[];
|
|
14
21
|
};
|
|
22
|
+
export type VideoInstr = {
|
|
23
|
+
/** Discriminator — required for video instructions. */
|
|
24
|
+
kind: "video";
|
|
25
|
+
/** When on the timeline the video starts, in milliseconds. */
|
|
26
|
+
at: number;
|
|
27
|
+
/** How long the clip plays, in ms. Defaults to the video's natural duration. */
|
|
28
|
+
duration?: number;
|
|
29
|
+
/** Start offset WITHIN the video file, in ms. Defaults to 0. */
|
|
30
|
+
seek?: number;
|
|
31
|
+
/** CSS selector for the `<video>` element. */
|
|
32
|
+
selector: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Drive a CSS-class-triggered animation (Animate.css, Magic, your own
|
|
36
|
+
* `@keyframes`) through the same timeline as everything else. The
|
|
37
|
+
* classes are added to the element, the resulting CSS animations are
|
|
38
|
+
* captured via `Element.getAnimations()`, and the helper drives their
|
|
39
|
+
* `currentTime` exactly like a WAAPI animation.
|
|
40
|
+
*
|
|
41
|
+
* Make sure the stylesheet that defines the keyframes is loaded *before*
|
|
42
|
+
* `defineTimeline` runs — otherwise the class addition produces no
|
|
43
|
+
* animation to drive.
|
|
44
|
+
*/
|
|
45
|
+
export type CssClassInstr = {
|
|
46
|
+
/** Discriminator — required. */
|
|
47
|
+
kind: "css";
|
|
48
|
+
/** When on the timeline the animation starts, in milliseconds. */
|
|
49
|
+
at: number;
|
|
50
|
+
/** How long the animation runs. Defaults to the longest CSS @keyframes duration on the element. */
|
|
51
|
+
duration?: number;
|
|
52
|
+
/** CSS selector for the target element. */
|
|
53
|
+
selector: string;
|
|
54
|
+
/** Class names to add, e.g. `["animate__animated", "animate__bounceInDown"]`. */
|
|
55
|
+
classes: string[];
|
|
56
|
+
};
|
|
57
|
+
export type Instr = AnimationInstr | VideoInstr | CssClassInstr;
|
|
15
58
|
export type TimelineSchema = {
|
|
16
59
|
duration: number;
|
|
17
|
-
instructions:
|
|
60
|
+
instructions: Instr[];
|
|
18
61
|
};
|
|
19
62
|
declare global {
|
|
20
63
|
interface Window {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,UAAU,GAAG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,yDAAyD;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,uDAAuD;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,gCAAgC;IAChC,IAAI,EAAE,KAAK,CAAC;IACZ,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,cAAc,GAAG,UAAU,GAAG,aAAa,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,KAAK,EAAE,CAAA;CAAE,CAAC;AAEzE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,EAAE;YAAE,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAC;QACrD,yBAAyB,CAAC,EAAE,OAAO,CAAC;KACrC;CACF;AAaD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAoH3D"}
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @maravilla-labs/frames — declare
|
|
3
|
-
*
|
|
2
|
+
* @maravilla-labs/frames — declare a timeline of animations, video, and
|
|
3
|
+
* CSS-class-driven animations (Animate.css, etc.) for a page. The same
|
|
4
|
+
* declaration works two ways:
|
|
4
5
|
*
|
|
5
|
-
* In a normal browser
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* them via `window.__mvFrames.applyState(t)` for deterministic output.
|
|
6
|
+
* - In a normal browser, each item plays at its scheduled `at` time.
|
|
7
|
+
* - Inside the Maravilla renderer, the timeline is stepped frame by
|
|
8
|
+
* frame to produce deterministic video output.
|
|
9
9
|
*/
|
|
10
10
|
export function defineTimeline(schema) {
|
|
11
11
|
const rendererPresent = typeof window !== "undefined" && window.__mvFramesRendererPresent === true;
|
|
12
|
-
const
|
|
12
|
+
const slots = schema.instructions.flatMap((i) => {
|
|
13
13
|
const el = document.querySelector(i.selector);
|
|
14
14
|
if (!el)
|
|
15
15
|
throw new Error(`defineTimeline: no element matches ${i.selector}`);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
if (i.kind === "video") {
|
|
17
|
+
if (!(el instanceof HTMLVideoElement)) {
|
|
18
|
+
throw new Error(`defineTimeline: ${i.selector} is not a <video> element`);
|
|
19
|
+
}
|
|
20
|
+
const seek = i.seek ?? 0;
|
|
21
|
+
const naturalMs = isFinite(el.duration) ? el.duration * 1000 : Infinity;
|
|
22
|
+
const duration = i.duration ?? Math.min(naturalMs, schema.duration - i.at);
|
|
23
|
+
el.pause();
|
|
24
|
+
el.currentTime = seek / 1000;
|
|
25
|
+
return [{ kind: "video", at: i.at, duration, seek, el }];
|
|
26
|
+
}
|
|
27
|
+
if (i.kind === "css") {
|
|
28
|
+
if (!(el instanceof HTMLElement)) {
|
|
29
|
+
throw new Error(`defineTimeline: ${i.selector} must be an HTMLElement for kind: "css"`);
|
|
30
|
+
}
|
|
31
|
+
// In browser preview, defer adding the classes until the scheduled
|
|
32
|
+
// time — the CSS animation then plays naturally from t=0.
|
|
33
|
+
if (!rendererPresent) {
|
|
34
|
+
return [{
|
|
35
|
+
kind: "css-pending",
|
|
36
|
+
at: i.at,
|
|
37
|
+
el,
|
|
38
|
+
classes: i.classes,
|
|
39
|
+
duration: i.duration ?? null,
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
// In renderer mode, add the classes now to materialise the CSS
|
|
43
|
+
// animations as WAAPI Animation objects, then pause and drive them
|
|
44
|
+
// via applyState.
|
|
45
|
+
const before = new Set(el.getAnimations());
|
|
46
|
+
el.classList.add(...i.classes);
|
|
47
|
+
const newAnims = el.getAnimations().filter((a) => !before.has(a));
|
|
48
|
+
if (newAnims.length === 0) {
|
|
49
|
+
throw new Error(`defineTimeline: classes [${i.classes.join(", ")}] produced no animation on ${i.selector}. ` +
|
|
50
|
+
`Is the stylesheet that defines those keyframes loaded before defineTimeline runs?`);
|
|
51
|
+
}
|
|
52
|
+
const natural = newAnims.reduce((max, a) => {
|
|
53
|
+
const t = a.effect?.getTiming();
|
|
54
|
+
const d = typeof t?.duration === "number" ? t.duration : 0;
|
|
55
|
+
return Math.max(max, d);
|
|
56
|
+
}, 0);
|
|
57
|
+
// Always honour the user's explicit `duration` (or fall back to the
|
|
58
|
+
// longest captured CSS animation). Override `animation-duration`
|
|
59
|
+
// inline with !important so a stylesheet's
|
|
60
|
+
// `@media (prefers-reduced-motion: reduce)` rule — which Animate.css
|
|
61
|
+
// ships and which collapses durations to 1ms — can't silently shrink
|
|
62
|
+
// the timeline. Users opt into this helper specifically to drive
|
|
63
|
+
// motion; "reduced motion" applies to involuntary motion, not the
|
|
64
|
+
// timeline they're authoring.
|
|
65
|
+
const duration = i.duration ?? (natural > 0 ? natural : Math.max(0, schema.duration - i.at));
|
|
66
|
+
el.style.setProperty("animation-duration", duration + "ms", "important");
|
|
67
|
+
return newAnims.map((anim) => {
|
|
68
|
+
anim.effect?.updateTiming({ duration });
|
|
69
|
+
anim.pause();
|
|
70
|
+
anim.currentTime = 0;
|
|
71
|
+
return { kind: "animation", at: i.at, duration, anim };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Standard WAAPI animation.
|
|
75
|
+
const duration = i.duration ?? Math.max(0, schema.duration - i.at);
|
|
76
|
+
const anim = el.animate(i.props, { duration, fill: "both" });
|
|
77
|
+
anim.pause();
|
|
78
|
+
return [{ kind: "animation", at: i.at, duration, anim }];
|
|
20
79
|
});
|
|
21
80
|
window.__mvFrames = {
|
|
22
81
|
async applyState(t) {
|
|
23
|
-
for (const
|
|
24
|
-
|
|
82
|
+
for (const s of slots) {
|
|
83
|
+
if (s.kind === "css-pending")
|
|
84
|
+
continue; // only relevant in browser preview
|
|
85
|
+
const local = clamp(t - s.at, 0, s.duration);
|
|
86
|
+
if (s.kind === "video") {
|
|
87
|
+
s.el.pause();
|
|
88
|
+
s.el.currentTime = (s.seek + local) / 1000;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
s.anim.currentTime = local;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
25
94
|
await document.fonts.ready;
|
|
26
95
|
},
|
|
27
96
|
};
|
|
97
|
+
if (!rendererPresent) {
|
|
98
|
+
for (const s of slots) {
|
|
99
|
+
window.setTimeout(() => {
|
|
100
|
+
if (s.kind === "video") {
|
|
101
|
+
s.el.currentTime = s.seek / 1000;
|
|
102
|
+
void s.el.play();
|
|
103
|
+
}
|
|
104
|
+
else if (s.kind === "css-pending") {
|
|
105
|
+
// Override `animation-duration` BEFORE adding the classes so
|
|
106
|
+
// the inline !important rule beats Animate.css's
|
|
107
|
+
// `prefers-reduced-motion` media query (which would otherwise
|
|
108
|
+
// collapse the animation to 1ms). If the user didn't pass an
|
|
109
|
+
// explicit duration, leave it alone and let the stylesheet's
|
|
110
|
+
// default apply.
|
|
111
|
+
if (s.duration !== null) {
|
|
112
|
+
s.el.style.setProperty("animation-duration", s.duration + "ms", "important");
|
|
113
|
+
}
|
|
114
|
+
s.el.classList.add(...s.classes);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
s.anim.play();
|
|
118
|
+
}
|
|
119
|
+
}, s.at);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function clamp(v, lo, hi) {
|
|
124
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
28
125
|
}
|
|
29
126
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyEH,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,MAAM,eAAe,GACnB,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,yBAAyB,KAAK,IAAI,CAAC;IAE7E,MAAM,KAAK,GAAW,MAAM,CAAC,YAAY,CAAC,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE;QAC5D,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE7E,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACvB,IAAI,CAAC,CAAC,EAAE,YAAY,gBAAgB,CAAC,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,QAAQ,2BAA2B,CAAC,CAAC;YAC5E,CAAC;YACD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YACzB,MAAM,SAAS,GAAG,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;YACxE,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3E,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,EAAE,CAAC,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;YAC7B,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3D,CAAC;QAED,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,EAAE,YAAY,WAAW,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,QAAQ,yCAAyC,CAAC,CAAC;YAC1F,CAAC;YACD,mEAAmE;YACnE,0DAA0D;YAC1D,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,OAAO,CAAC;wBACN,IAAI,EAAE,aAAa;wBACnB,EAAE,EAAE,CAAC,CAAC,EAAE;wBACR,EAAE;wBACF,OAAO,EAAE,CAAC,CAAC,OAAO;wBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;qBAC7B,CAAC,CAAC;YACL,CAAC;YACD,+DAA+D;YAC/D,mEAAmE;YACnE,kBAAkB;YAClB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;YAC3C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;YAC/B,MAAM,QAAQ,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAClE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CACb,4BAA4B,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,QAAQ,IAAI;oBAC1F,mFAAmF,CACtF,CAAC;YACJ,CAAC;YACD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;gBACzC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;gBAChC,MAAM,CAAC,GAAG,OAAO,CAAC,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC3D,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC1B,CAAC,EAAE,CAAC,CAAC,CAAC;YACN,oEAAoE;YACpE,iEAAiE;YACjE,2CAA2C;YAC3C,qEAAqE;YACrE,qEAAqE;YACrE,iEAAiE;YACjE,kEAAkE;YAClE,8BAA8B;YAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7F,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,oBAAoB,EAAE,QAAQ,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC;YACzE,OAAO,QAAQ,CAAC,GAAG,CAAW,CAAC,IAAI,EAAE,EAAE;gBACrC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;gBACxC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;gBACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACzD,CAAC,CAAC,CAAC;QACL,CAAC;QAED,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,UAAU,GAAG;QAClB,KAAK,CAAC,UAAU,CAAC,CAAS;YACxB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa;oBAAE,SAAS,CAAC,mCAAmC;gBAC3E,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;gBAC7C,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBACvB,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;oBACb,CAAC,CAAC,EAAE,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;gBAC7C,CAAC;qBAAM,CAAC;oBACN,CAAC,CAAC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;gBAC7B,CAAC;YACH,CAAC;YACD,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;QAC7B,CAAC;KACF,CAAC;IAEF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;gBACrB,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBACvB,CAAC,CAAC,EAAE,CAAC,WAAW,GAAG,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;oBACjC,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;gBACnB,CAAC;qBAAM,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;oBACpC,6DAA6D;oBAC7D,iDAAiD;oBACjD,8DAA8D;oBAC9D,6DAA6D;oBAC7D,6DAA6D;oBAC7D,iBAAiB;oBACjB,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;wBACxB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC,CAAC,QAAQ,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC;oBAC/E,CAAC;oBACD,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;gBACnC,CAAC;qBAAM,CAAC;oBACN,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBAChB,CAAC;YACH,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,CAAS,EAAE,EAAU,EAAE,EAAU;IAC9C,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maravilla-labs/frames",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Tiny vanilla helper for declaring Web Animations API timelines that the Maravilla runtime renderer drives frame-accurately.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @maravilla-labs/frames — declare
|
|
3
|
-
*
|
|
2
|
+
* @maravilla-labs/frames — declare a timeline of animations, video, and
|
|
3
|
+
* CSS-class-driven animations (Animate.css, etc.) for a page. The same
|
|
4
|
+
* declaration works two ways:
|
|
4
5
|
*
|
|
5
|
-
* In a normal browser
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* them via `window.__mvFrames.applyState(t)` for deterministic output.
|
|
6
|
+
* - In a normal browser, each item plays at its scheduled `at` time.
|
|
7
|
+
* - Inside the Maravilla renderer, the timeline is stepped frame by
|
|
8
|
+
* frame to produce deterministic video output.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
export type
|
|
12
|
-
|
|
11
|
+
export type AnimationInstr = {
|
|
12
|
+
/** Optional discriminator. Defaults to `"animation"`. */
|
|
13
|
+
kind?: "animation";
|
|
14
|
+
/** When on the timeline this animation starts, in milliseconds. */
|
|
15
|
+
at: number;
|
|
16
|
+
/** How long the animation runs from `at`, in ms. Defaults to (schema.duration - at). */
|
|
17
|
+
duration?: number;
|
|
18
|
+
/** CSS selector for the element being animated. */
|
|
19
|
+
selector: string;
|
|
20
|
+
/** Web Animations API keyframes, e.g. `[{ opacity: 0 }, { opacity: 1 }]`. */
|
|
21
|
+
props: Keyframe[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type VideoInstr = {
|
|
25
|
+
/** Discriminator — required for video instructions. */
|
|
26
|
+
kind: "video";
|
|
27
|
+
/** When on the timeline the video starts, in milliseconds. */
|
|
28
|
+
at: number;
|
|
29
|
+
/** How long the clip plays, in ms. Defaults to the video's natural duration. */
|
|
30
|
+
duration?: number;
|
|
31
|
+
/** Start offset WITHIN the video file, in ms. Defaults to 0. */
|
|
32
|
+
seek?: number;
|
|
33
|
+
/** CSS selector for the `<video>` element. */
|
|
34
|
+
selector: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Drive a CSS-class-triggered animation (Animate.css, Magic, your own
|
|
39
|
+
* `@keyframes`) through the same timeline as everything else. The
|
|
40
|
+
* classes are added to the element, the resulting CSS animations are
|
|
41
|
+
* captured via `Element.getAnimations()`, and the helper drives their
|
|
42
|
+
* `currentTime` exactly like a WAAPI animation.
|
|
43
|
+
*
|
|
44
|
+
* Make sure the stylesheet that defines the keyframes is loaded *before*
|
|
45
|
+
* `defineTimeline` runs — otherwise the class addition produces no
|
|
46
|
+
* animation to drive.
|
|
47
|
+
*/
|
|
48
|
+
export type CssClassInstr = {
|
|
49
|
+
/** Discriminator — required. */
|
|
50
|
+
kind: "css";
|
|
51
|
+
/** When on the timeline the animation starts, in milliseconds. */
|
|
52
|
+
at: number;
|
|
53
|
+
/** How long the animation runs. Defaults to the longest CSS @keyframes duration on the element. */
|
|
54
|
+
duration?: number;
|
|
55
|
+
/** CSS selector for the target element. */
|
|
56
|
+
selector: string;
|
|
57
|
+
/** Class names to add, e.g. `["animate__animated", "animate__bounceInDown"]`. */
|
|
58
|
+
classes: string[];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type Instr = AnimationInstr | VideoInstr | CssClassInstr;
|
|
62
|
+
export type TimelineSchema = { duration: number; instructions: Instr[] };
|
|
13
63
|
|
|
14
64
|
declare global {
|
|
15
65
|
interface Window {
|
|
@@ -18,22 +68,135 @@ declare global {
|
|
|
18
68
|
}
|
|
19
69
|
}
|
|
20
70
|
|
|
21
|
-
|
|
71
|
+
type AnimSlot = { kind: "animation"; at: number; duration: number; anim: Animation };
|
|
72
|
+
type VideoSlot = { kind: "video"; at: number; duration: number; seek: number; el: HTMLVideoElement };
|
|
73
|
+
type CssPendingSlot = {
|
|
74
|
+
kind: "css-pending";
|
|
75
|
+
at: number;
|
|
76
|
+
el: HTMLElement;
|
|
77
|
+
classes: string[];
|
|
78
|
+
duration: number | null;
|
|
79
|
+
};
|
|
80
|
+
type Slot = AnimSlot | VideoSlot | CssPendingSlot;
|
|
81
|
+
|
|
82
|
+
export function defineTimeline(schema: TimelineSchema): void {
|
|
22
83
|
const rendererPresent =
|
|
23
84
|
typeof window !== "undefined" && window.__mvFramesRendererPresent === true;
|
|
24
85
|
|
|
25
|
-
const
|
|
86
|
+
const slots: Slot[] = schema.instructions.flatMap<Slot>((i) => {
|
|
26
87
|
const el = document.querySelector(i.selector);
|
|
27
88
|
if (!el) throw new Error(`defineTimeline: no element matches ${i.selector}`);
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
89
|
+
|
|
90
|
+
if (i.kind === "video") {
|
|
91
|
+
if (!(el instanceof HTMLVideoElement)) {
|
|
92
|
+
throw new Error(`defineTimeline: ${i.selector} is not a <video> element`);
|
|
93
|
+
}
|
|
94
|
+
const seek = i.seek ?? 0;
|
|
95
|
+
const naturalMs = isFinite(el.duration) ? el.duration * 1000 : Infinity;
|
|
96
|
+
const duration = i.duration ?? Math.min(naturalMs, schema.duration - i.at);
|
|
97
|
+
el.pause();
|
|
98
|
+
el.currentTime = seek / 1000;
|
|
99
|
+
return [{ kind: "video", at: i.at, duration, seek, el }];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (i.kind === "css") {
|
|
103
|
+
if (!(el instanceof HTMLElement)) {
|
|
104
|
+
throw new Error(`defineTimeline: ${i.selector} must be an HTMLElement for kind: "css"`);
|
|
105
|
+
}
|
|
106
|
+
// In browser preview, defer adding the classes until the scheduled
|
|
107
|
+
// time — the CSS animation then plays naturally from t=0.
|
|
108
|
+
if (!rendererPresent) {
|
|
109
|
+
return [{
|
|
110
|
+
kind: "css-pending",
|
|
111
|
+
at: i.at,
|
|
112
|
+
el,
|
|
113
|
+
classes: i.classes,
|
|
114
|
+
duration: i.duration ?? null,
|
|
115
|
+
}];
|
|
116
|
+
}
|
|
117
|
+
// In renderer mode, add the classes now to materialise the CSS
|
|
118
|
+
// animations as WAAPI Animation objects, then pause and drive them
|
|
119
|
+
// via applyState.
|
|
120
|
+
const before = new Set(el.getAnimations());
|
|
121
|
+
el.classList.add(...i.classes);
|
|
122
|
+
const newAnims = el.getAnimations().filter((a) => !before.has(a));
|
|
123
|
+
if (newAnims.length === 0) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`defineTimeline: classes [${i.classes.join(", ")}] produced no animation on ${i.selector}. ` +
|
|
126
|
+
`Is the stylesheet that defines those keyframes loaded before defineTimeline runs?`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const natural = newAnims.reduce((max, a) => {
|
|
130
|
+
const t = a.effect?.getTiming();
|
|
131
|
+
const d = typeof t?.duration === "number" ? t.duration : 0;
|
|
132
|
+
return Math.max(max, d);
|
|
133
|
+
}, 0);
|
|
134
|
+
// Always honour the user's explicit `duration` (or fall back to the
|
|
135
|
+
// longest captured CSS animation). Override `animation-duration`
|
|
136
|
+
// inline with !important so a stylesheet's
|
|
137
|
+
// `@media (prefers-reduced-motion: reduce)` rule — which Animate.css
|
|
138
|
+
// ships and which collapses durations to 1ms — can't silently shrink
|
|
139
|
+
// the timeline. Users opt into this helper specifically to drive
|
|
140
|
+
// motion; "reduced motion" applies to involuntary motion, not the
|
|
141
|
+
// timeline they're authoring.
|
|
142
|
+
const duration = i.duration ?? (natural > 0 ? natural : Math.max(0, schema.duration - i.at));
|
|
143
|
+
el.style.setProperty("animation-duration", duration + "ms", "important");
|
|
144
|
+
return newAnims.map<AnimSlot>((anim) => {
|
|
145
|
+
anim.effect?.updateTiming({ duration });
|
|
146
|
+
anim.pause();
|
|
147
|
+
anim.currentTime = 0;
|
|
148
|
+
return { kind: "animation", at: i.at, duration, anim };
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Standard WAAPI animation.
|
|
153
|
+
const duration = i.duration ?? Math.max(0, schema.duration - i.at);
|
|
154
|
+
const anim = el.animate(i.props, { duration, fill: "both" });
|
|
155
|
+
anim.pause();
|
|
156
|
+
return [{ kind: "animation", at: i.at, duration, anim }];
|
|
31
157
|
});
|
|
32
158
|
|
|
33
159
|
window.__mvFrames = {
|
|
34
160
|
async applyState(t: number) {
|
|
35
|
-
for (const
|
|
161
|
+
for (const s of slots) {
|
|
162
|
+
if (s.kind === "css-pending") continue; // only relevant in browser preview
|
|
163
|
+
const local = clamp(t - s.at, 0, s.duration);
|
|
164
|
+
if (s.kind === "video") {
|
|
165
|
+
s.el.pause();
|
|
166
|
+
s.el.currentTime = (s.seek + local) / 1000;
|
|
167
|
+
} else {
|
|
168
|
+
s.anim.currentTime = local;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
36
171
|
await document.fonts.ready;
|
|
37
172
|
},
|
|
38
173
|
};
|
|
174
|
+
|
|
175
|
+
if (!rendererPresent) {
|
|
176
|
+
for (const s of slots) {
|
|
177
|
+
window.setTimeout(() => {
|
|
178
|
+
if (s.kind === "video") {
|
|
179
|
+
s.el.currentTime = s.seek / 1000;
|
|
180
|
+
void s.el.play();
|
|
181
|
+
} else if (s.kind === "css-pending") {
|
|
182
|
+
// Override `animation-duration` BEFORE adding the classes so
|
|
183
|
+
// the inline !important rule beats Animate.css's
|
|
184
|
+
// `prefers-reduced-motion` media query (which would otherwise
|
|
185
|
+
// collapse the animation to 1ms). If the user didn't pass an
|
|
186
|
+
// explicit duration, leave it alone and let the stylesheet's
|
|
187
|
+
// default apply.
|
|
188
|
+
if (s.duration !== null) {
|
|
189
|
+
s.el.style.setProperty("animation-duration", s.duration + "ms", "important");
|
|
190
|
+
}
|
|
191
|
+
s.el.classList.add(...s.classes);
|
|
192
|
+
} else {
|
|
193
|
+
s.anim.play();
|
|
194
|
+
}
|
|
195
|
+
}, s.at);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function clamp(v: number, lo: number, hi: number): number {
|
|
201
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
39
202
|
}
|