@editframe/create 0.44.0 → 0.45.0
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/dist/index.js +16 -28
- package/dist/index.js.map +1 -1
- package/dist/skills/editframe-brand-video-generator/README.md +155 -0
- package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
- package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
- package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
- package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
- package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
- package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
- package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
- package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
- package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
- package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
- package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
- package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
- package/dist/skills/editframe-composition/SKILL.md +169 -0
- package/dist/skills/editframe-composition/references/audio.md +483 -0
- package/dist/skills/editframe-composition/references/captions.md +844 -0
- package/dist/skills/editframe-composition/references/composition-model.md +73 -0
- package/dist/skills/editframe-composition/references/configuration.md +403 -0
- package/dist/skills/editframe-composition/references/css-parts.md +105 -0
- package/dist/skills/editframe-composition/references/css-variables.md +640 -0
- package/dist/skills/editframe-composition/references/entry-points.md +810 -0
- package/dist/skills/editframe-composition/references/events.md +499 -0
- package/dist/skills/editframe-composition/references/getting-started.md +259 -0
- package/dist/skills/editframe-composition/references/hooks.md +234 -0
- package/dist/skills/editframe-composition/references/image.md +241 -0
- package/dist/skills/editframe-composition/references/r3f.md +580 -0
- package/dist/skills/editframe-composition/references/render-api.md +484 -0
- package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
- package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
- package/dist/skills/editframe-composition/references/scripting.md +606 -0
- package/dist/skills/editframe-composition/references/sequencing.md +116 -0
- package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
- package/dist/skills/editframe-composition/references/surface.md +329 -0
- package/dist/skills/editframe-composition/references/text.md +627 -0
- package/dist/skills/editframe-composition/references/time-model.md +99 -0
- package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
- package/dist/skills/editframe-composition/references/timegroup.md +457 -0
- package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
- package/dist/skills/editframe-composition/references/transcription.md +47 -0
- package/dist/skills/editframe-composition/references/transitions.md +608 -0
- package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
- package/dist/skills/editframe-composition/references/video.md +506 -0
- package/dist/skills/editframe-composition/references/waveform.md +327 -0
- package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
- package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
- package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
- package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
- package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
- package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
- package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
- package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
- package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
- package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
- package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
- package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
- package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
- package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
- package/dist/skills/editframe-editor-gui/references/play.md +370 -0
- package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
- package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
- package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
- package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
- package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
- package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
- package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
- package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
- package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
- package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
- package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
- package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
- package/dist/skills/editframe-motion-design/SKILL.md +101 -0
- package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
- package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
- package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
- package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
- package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
- package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
- package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
- package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
- package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
- package/dist/skills/editframe-webhooks/SKILL.md +126 -0
- package/dist/skills/editframe-webhooks/references/events.md +382 -0
- package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
- package/dist/skills/editframe-webhooks/references/security.md +418 -0
- package/dist/skills/editframe-webhooks/references/testing.md +409 -0
- package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
- package/dist/templates/html/AGENTS.md +13 -0
- package/dist/templates/react/AGENTS.md +13 -0
- package/dist/utils.js +15 -16
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/tsdown.config.ts +4 -0
- package/dist/detectAgent.js +0 -89
- package/dist/detectAgent.js.map +0 -1
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scripting
|
|
3
|
+
description: Add dynamic JavaScript behavior to timegroups with initializer callbacks and per-frame task functions for procedural animation.
|
|
4
|
+
type: reference
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Rendering"
|
|
7
|
+
priority: 20
|
|
8
|
+
related: ["timegroup"]
|
|
9
|
+
react:
|
|
10
|
+
generate: true
|
|
11
|
+
componentName: "Scripting"
|
|
12
|
+
importPath: "@editframe/react"
|
|
13
|
+
nav:
|
|
14
|
+
parent: "Advanced"
|
|
15
|
+
priority: 52
|
|
16
|
+
related: ["hooks", "timegroup"]
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Scripting
|
|
20
|
+
|
|
21
|
+
<!-- html-only -->
|
|
22
|
+
Add dynamic JavaScript behavior to timegroups.
|
|
23
|
+
<!-- /html-only -->
|
|
24
|
+
<!-- react-only -->
|
|
25
|
+
Add dynamic JavaScript behavior to timegroups using React refs.
|
|
26
|
+
<!-- /react-only -->
|
|
27
|
+
|
|
28
|
+
## Initializer
|
|
29
|
+
|
|
30
|
+
<!-- html-only -->
|
|
31
|
+
Set up behavior that runs once per instance (prime timeline and render clones).
|
|
32
|
+
|
|
33
|
+
### Basic Usage
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<ef-timegroup id="my-scene" mode="fixed" duration="5s">
|
|
37
|
+
<div class="content"></div>
|
|
38
|
+
</ef-timegroup>
|
|
39
|
+
|
|
40
|
+
<script>
|
|
41
|
+
const tg = document.querySelector('#my-scene');
|
|
42
|
+
tg.initializer = (instance) => {
|
|
43
|
+
// Runs once on prime timeline, once on each render clone
|
|
44
|
+
console.log('Initializer running');
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
47
|
+
```
|
|
48
|
+
<!-- /html-only -->
|
|
49
|
+
<!-- react-only -->
|
|
50
|
+
Set up behavior that runs once when the element is initialized.
|
|
51
|
+
|
|
52
|
+
### Basic Usage with Refs
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { useRef, useEffect } from "react";
|
|
56
|
+
import { Timegroup } from "@editframe/react";
|
|
57
|
+
|
|
58
|
+
const DynamicScene = () => {
|
|
59
|
+
const timegroupRef = useRef<HTMLElement>(null);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const tg = timegroupRef.current;
|
|
63
|
+
if (!tg) return;
|
|
64
|
+
|
|
65
|
+
tg.initializer = (instance) => {
|
|
66
|
+
// Runs once when element is initialized
|
|
67
|
+
console.log('Initializer running');
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Timegroup ref={timegroupRef} mode="fixed" duration="5s">
|
|
73
|
+
<div>Content</div>
|
|
74
|
+
</Timegroup>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
<!-- /react-only -->
|
|
79
|
+
|
|
80
|
+
### Constraints
|
|
81
|
+
|
|
82
|
+
- **Must be synchronous** - No async/await, no Promise return
|
|
83
|
+
<!-- html-only -->
|
|
84
|
+
- **Must complete quickly** - Less than 100ms (error thrown) or less than 10ms (warning logged)
|
|
85
|
+
<!-- /html-only -->
|
|
86
|
+
<!-- react-only -->
|
|
87
|
+
- **Must complete quickly** - <100ms (error thrown) or <10ms (warning logged)
|
|
88
|
+
<!-- /react-only -->
|
|
89
|
+
- **Register callbacks only** - Don't do expensive work in initializer
|
|
90
|
+
|
|
91
|
+
### Timing
|
|
92
|
+
|
|
93
|
+
- Set before connection: Runs after element connects to DOM
|
|
94
|
+
- Set after connection: Runs immediately
|
|
95
|
+
<!-- html-only -->
|
|
96
|
+
- Clones: Automatically copy and run initializer
|
|
97
|
+
<!-- /html-only -->
|
|
98
|
+
|
|
99
|
+
## Frame Tasks
|
|
100
|
+
|
|
101
|
+
Register callbacks that execute on each frame during rendering.
|
|
102
|
+
|
|
103
|
+
### addFrameTask()
|
|
104
|
+
|
|
105
|
+
<!-- html-only -->
|
|
106
|
+
```javascript
|
|
107
|
+
const tg = document.querySelector('ef-timegroup');
|
|
108
|
+
tg.initializer = (instance) => {
|
|
109
|
+
const cleanup = instance.addFrameTask((info) => {
|
|
110
|
+
// Called on each frame
|
|
111
|
+
// info contains: ownCurrentTimeMs, durationMs, percentComplete, etc.
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// cleanup() removes the callback when called
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
<!-- /html-only -->
|
|
118
|
+
<!-- react-only -->
|
|
119
|
+
```tsx
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
const tg = timegroupRef.current;
|
|
122
|
+
if (!tg) return;
|
|
123
|
+
|
|
124
|
+
tg.initializer = (instance) => {
|
|
125
|
+
const cleanup = instance.addFrameTask((info) => {
|
|
126
|
+
// Called on each frame
|
|
127
|
+
// info contains: ownCurrentTimeMs, durationMs, percentComplete, etc.
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return cleanup;
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
133
|
+
```
|
|
134
|
+
<!-- /react-only -->
|
|
135
|
+
|
|
136
|
+
### Callback Info
|
|
137
|
+
|
|
138
|
+
Frame task callbacks receive timing information:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
instance.addFrameTask((info) => {
|
|
142
|
+
console.log(info.ownCurrentTimeMs); // Current time in ms
|
|
143
|
+
console.log(info.durationMs); // Total duration
|
|
144
|
+
console.log(info.percentComplete); // 0-1 progress
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Multiple Callbacks
|
|
149
|
+
|
|
150
|
+
Register multiple frame tasks - they execute in parallel:
|
|
151
|
+
|
|
152
|
+
<!-- html-only -->
|
|
153
|
+
```javascript
|
|
154
|
+
tg.initializer = (instance) => {
|
|
155
|
+
instance.addFrameTask((info) => {
|
|
156
|
+
// Update text
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
instance.addFrameTask((info) => {
|
|
160
|
+
// Update position
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
<!-- /html-only -->
|
|
165
|
+
<!-- react-only -->
|
|
166
|
+
```tsx
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
const tg = timegroupRef.current;
|
|
169
|
+
if (!tg) return;
|
|
170
|
+
|
|
171
|
+
tg.initializer = (instance) => {
|
|
172
|
+
const cleanup1 = instance.addFrameTask((info) => {
|
|
173
|
+
// Update text
|
|
174
|
+
const text = instance.querySelector('.text');
|
|
175
|
+
if (text) {
|
|
176
|
+
text.textContent = `${info.ownCurrentTimeMs}ms`;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const cleanup2 = instance.addFrameTask((info) => {
|
|
181
|
+
// Update position
|
|
182
|
+
const box = instance.querySelector('.box');
|
|
183
|
+
if (box) {
|
|
184
|
+
(box as HTMLElement).style.left = `${info.percentComplete * 100}%`;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Return combined cleanup
|
|
189
|
+
return () => {
|
|
190
|
+
cleanup1();
|
|
191
|
+
cleanup2();
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
}, []);
|
|
195
|
+
```
|
|
196
|
+
<!-- /react-only -->
|
|
197
|
+
|
|
198
|
+
<!-- react-only -->
|
|
199
|
+
### Cleanup Pattern
|
|
200
|
+
|
|
201
|
+
Use React's useEffect cleanup for proper teardown:
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const tg = timegroupRef.current;
|
|
206
|
+
if (!tg) return;
|
|
207
|
+
|
|
208
|
+
tg.initializer = (instance) => {
|
|
209
|
+
const cleanup = instance.addFrameTask((info) => {
|
|
210
|
+
// Frame callback logic
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return cleanup;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Cleanup on unmount
|
|
217
|
+
return () => {
|
|
218
|
+
if (tg) {
|
|
219
|
+
tg.initializer = undefined;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}, []);
|
|
223
|
+
```
|
|
224
|
+
<!-- /react-only -->
|
|
225
|
+
|
|
226
|
+
## Examples
|
|
227
|
+
|
|
228
|
+
### Dynamic Text Updates
|
|
229
|
+
|
|
230
|
+
<!-- html-only -->
|
|
231
|
+
```html
|
|
232
|
+
<ef-timegroup id="counter" mode="fixed" duration="10s">
|
|
233
|
+
<div class="text-4xl text-white counter-text"></div>
|
|
234
|
+
</ef-timegroup>
|
|
235
|
+
|
|
236
|
+
<script>
|
|
237
|
+
const tg = document.querySelector('#counter');
|
|
238
|
+
tg.initializer = (instance) => {
|
|
239
|
+
instance.addFrameTask((info) => {
|
|
240
|
+
const text = instance.querySelector('.counter-text');
|
|
241
|
+
const seconds = (info.ownCurrentTimeMs / 1000).toFixed(2);
|
|
242
|
+
text.textContent = `Time: ${seconds}s`;
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
</script>
|
|
246
|
+
```
|
|
247
|
+
<!-- /html-only -->
|
|
248
|
+
<!-- react-only -->
|
|
249
|
+
```tsx
|
|
250
|
+
import { useRef, useEffect } from "react";
|
|
251
|
+
import { Timegroup } from "@editframe/react";
|
|
252
|
+
|
|
253
|
+
const Counter = () => {
|
|
254
|
+
const timegroupRef = useRef<HTMLElement>(null);
|
|
255
|
+
const textRef = useRef<HTMLDivElement>(null);
|
|
256
|
+
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
const tg = timegroupRef.current;
|
|
259
|
+
if (!tg) return;
|
|
260
|
+
|
|
261
|
+
tg.initializer = (instance) => {
|
|
262
|
+
return instance.addFrameTask((info) => {
|
|
263
|
+
const text = instance.querySelector('.counter-text');
|
|
264
|
+
if (text) {
|
|
265
|
+
const seconds = (info.ownCurrentTimeMs / 1000).toFixed(2);
|
|
266
|
+
text.textContent = `Time: ${seconds}s`;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
}, []);
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<Timegroup ref={timegroupRef} mode="fixed" duration="10s">
|
|
274
|
+
<div ref={textRef} className="text-4xl text-white counter-text" />
|
|
275
|
+
</Timegroup>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
<!-- /react-only -->
|
|
280
|
+
|
|
281
|
+
### Procedural Animation
|
|
282
|
+
|
|
283
|
+
<!-- html-only -->
|
|
284
|
+
```html
|
|
285
|
+
<ef-timegroup id="animated" mode="fixed" duration="5s">
|
|
286
|
+
<div class="box"></div>
|
|
287
|
+
</ef-timegroup>
|
|
288
|
+
|
|
289
|
+
<script>
|
|
290
|
+
const tg = document.querySelector('#animated');
|
|
291
|
+
tg.initializer = (instance) => {
|
|
292
|
+
instance.addFrameTask((info) => {
|
|
293
|
+
const box = instance.querySelector('.box');
|
|
294
|
+
const progress = info.percentComplete;
|
|
295
|
+
|
|
296
|
+
// Move box across screen
|
|
297
|
+
box.style.transform = `translateX(${progress * 500}px)`;
|
|
298
|
+
|
|
299
|
+
// Rotate based on time
|
|
300
|
+
const rotation = (info.ownCurrentTimeMs / 10) % 360;
|
|
301
|
+
box.style.transform += ` rotate(${rotation}deg)`;
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
</script>
|
|
305
|
+
```
|
|
306
|
+
<!-- /html-only -->
|
|
307
|
+
<!-- react-only -->
|
|
308
|
+
```tsx
|
|
309
|
+
const AnimatedBox = () => {
|
|
310
|
+
const timegroupRef = useRef<HTMLElement>(null);
|
|
311
|
+
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
const tg = timegroupRef.current;
|
|
314
|
+
if (!tg) return;
|
|
315
|
+
|
|
316
|
+
tg.initializer = (instance) => {
|
|
317
|
+
return instance.addFrameTask((info) => {
|
|
318
|
+
const box = instance.querySelector('.box');
|
|
319
|
+
if (!box) return;
|
|
320
|
+
|
|
321
|
+
const progress = info.percentComplete;
|
|
322
|
+
|
|
323
|
+
// Move box across screen
|
|
324
|
+
const x = progress * 500;
|
|
325
|
+
|
|
326
|
+
// Rotate based on time
|
|
327
|
+
const rotation = (info.ownCurrentTimeMs / 10) % 360;
|
|
328
|
+
|
|
329
|
+
(box as HTMLElement).style.transform =
|
|
330
|
+
`translateX(${x}px) rotate(${rotation}deg)`;
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
}, []);
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<Timegroup ref={timegroupRef} mode="fixed" duration="5s" className="relative w-full h-full">
|
|
337
|
+
<div className="box w-24 h-24 bg-red-500" />
|
|
338
|
+
</Timegroup>
|
|
339
|
+
);
|
|
340
|
+
};
|
|
341
|
+
```
|
|
342
|
+
<!-- /react-only -->
|
|
343
|
+
|
|
344
|
+
### Data-Driven Content
|
|
345
|
+
|
|
346
|
+
<!-- html-only -->
|
|
347
|
+
```html
|
|
348
|
+
<ef-timegroup id="data-scene" mode="fixed" duration="8s">
|
|
349
|
+
<div class="data-display"></div>
|
|
350
|
+
</ef-timegroup>
|
|
351
|
+
|
|
352
|
+
<script>
|
|
353
|
+
const data = [
|
|
354
|
+
{ time: 0, value: 10 },
|
|
355
|
+
{ time: 2000, value: 25 },
|
|
356
|
+
{ time: 4000, value: 40 },
|
|
357
|
+
{ time: 6000, value: 60 },
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
const tg = document.querySelector('#data-scene');
|
|
361
|
+
tg.initializer = (instance) => {
|
|
362
|
+
instance.addFrameTask((info) => {
|
|
363
|
+
const display = instance.querySelector('.data-display');
|
|
364
|
+
|
|
365
|
+
// Find current data point
|
|
366
|
+
const current = data.find((d, i) => {
|
|
367
|
+
const next = data[i + 1];
|
|
368
|
+
return info.ownCurrentTimeMs >= d.time &&
|
|
369
|
+
(!next || info.ownCurrentTimeMs < next.time);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (current) {
|
|
373
|
+
display.textContent = `Value: ${current.value}`;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
};
|
|
377
|
+
</script>
|
|
378
|
+
```
|
|
379
|
+
<!-- /html-only -->
|
|
380
|
+
<!-- react-only -->
|
|
381
|
+
```tsx
|
|
382
|
+
interface DataPoint {
|
|
383
|
+
time: number;
|
|
384
|
+
value: number;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const DataScene = ({ data }: { data: DataPoint[] }) => {
|
|
388
|
+
const timegroupRef = useRef<HTMLElement>(null);
|
|
389
|
+
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
const tg = timegroupRef.current;
|
|
392
|
+
if (!tg) return;
|
|
393
|
+
|
|
394
|
+
tg.initializer = (instance) => {
|
|
395
|
+
return instance.addFrameTask((info) => {
|
|
396
|
+
const display = instance.querySelector('.data-display');
|
|
397
|
+
if (!display) return;
|
|
398
|
+
|
|
399
|
+
// Find current data point
|
|
400
|
+
const current = data.find((d, i) => {
|
|
401
|
+
const next = data[i + 1];
|
|
402
|
+
return info.ownCurrentTimeMs >= d.time &&
|
|
403
|
+
(!next || info.ownCurrentTimeMs < next.time);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
if (current) {
|
|
407
|
+
display.textContent = `Value: ${current.value}`;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
}, [data]);
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<Timegroup ref={timegroupRef} mode="fixed" duration="8s">
|
|
415
|
+
<div className="data-display text-2xl" />
|
|
416
|
+
</Timegroup>
|
|
417
|
+
);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Usage
|
|
421
|
+
const data = [
|
|
422
|
+
{ time: 0, value: 10 },
|
|
423
|
+
{ time: 2000, value: 25 },
|
|
424
|
+
{ time: 4000, value: 40 },
|
|
425
|
+
{ time: 6000, value: 60 },
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
<DataScene data={data} />
|
|
429
|
+
```
|
|
430
|
+
<!-- /react-only -->
|
|
431
|
+
|
|
432
|
+
<!-- html-only -->
|
|
433
|
+
### Cleanup Pattern
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
tg.initializer = (instance) => {
|
|
437
|
+
// Set up resources
|
|
438
|
+
const state = { count: 0 };
|
|
439
|
+
|
|
440
|
+
const cleanup = instance.addFrameTask((info) => {
|
|
441
|
+
state.count++;
|
|
442
|
+
console.log(`Frame ${state.count}`);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Cleanup is automatic when instance is removed
|
|
446
|
+
// But you can manually cleanup if needed:
|
|
447
|
+
// cleanup();
|
|
448
|
+
};
|
|
449
|
+
```
|
|
450
|
+
<!-- /html-only -->
|
|
451
|
+
|
|
452
|
+
<!-- react-only -->
|
|
453
|
+
### Integration with React State
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
const InteractiveScene = () => {
|
|
457
|
+
const timegroupRef = useRef<HTMLElement>(null);
|
|
458
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
459
|
+
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
const tg = timegroupRef.current;
|
|
462
|
+
if (!tg) return;
|
|
463
|
+
|
|
464
|
+
tg.initializer = (instance) => {
|
|
465
|
+
return instance.addFrameTask((info) => {
|
|
466
|
+
// Update React state (throttle for performance)
|
|
467
|
+
const timeInSeconds = Math.floor(info.ownCurrentTimeMs / 1000);
|
|
468
|
+
setCurrentTime(timeInSeconds);
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
}, []);
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<>
|
|
475
|
+
<div className="text-white mb-4">Current Time: {currentTime}s</div>
|
|
476
|
+
<Timegroup ref={timegroupRef} mode="fixed" duration="10s">
|
|
477
|
+
<div className="content">Scene content</div>
|
|
478
|
+
</Timegroup>
|
|
479
|
+
</>
|
|
480
|
+
);
|
|
481
|
+
};
|
|
482
|
+
```
|
|
483
|
+
<!-- /react-only -->
|
|
484
|
+
|
|
485
|
+
## <!-- html-only -->Prime Timeline vs Render Clone<!-- /html-only --><!-- react-only -->Consistent Behavior<!-- /react-only -->
|
|
486
|
+
|
|
487
|
+
<!-- html-only -->
|
|
488
|
+
The initializer runs on both:
|
|
489
|
+
|
|
490
|
+
- **Prime timeline**: Interactive preview in browser
|
|
491
|
+
- **Render clone**: Headless rendering for video export
|
|
492
|
+
|
|
493
|
+
Same code runs in both contexts, ensuring consistent behavior.
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
tg.initializer = (instance) => {
|
|
497
|
+
// This code runs identically on prime timeline and render clones
|
|
498
|
+
instance.addFrameTask((info) => {
|
|
499
|
+
// Update content based on time
|
|
500
|
+
});
|
|
501
|
+
};
|
|
502
|
+
```
|
|
503
|
+
<!-- /html-only -->
|
|
504
|
+
<!-- react-only -->
|
|
505
|
+
The initializer ensures your code runs consistently in both preview and rendering:
|
|
506
|
+
|
|
507
|
+
```tsx
|
|
508
|
+
tg.initializer = (instance) => {
|
|
509
|
+
// This code runs the same way in preview and rendering
|
|
510
|
+
return instance.addFrameTask((info) => {
|
|
511
|
+
// Update content based on time
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Important**: For React components, you must use `TimelineRoot` to ensure React hooks and state work correctly. See [timeline-root.md](references/timeline-root.md) for details.
|
|
517
|
+
<!-- /react-only -->
|
|
518
|
+
|
|
519
|
+
## Best Practices
|
|
520
|
+
|
|
521
|
+
<!-- html-only -->
|
|
522
|
+
1. **Keep initializer fast** - Register callbacks, don't do heavy work
|
|
523
|
+
2. **Use frame tasks for updates** - All time-based logic goes in frame callbacks
|
|
524
|
+
3. **Avoid side effects** - Don't modify external state, keep logic contained
|
|
525
|
+
4. **Test in both contexts** - Preview in browser AND render to video
|
|
526
|
+
5. **Handle missing elements** - Check if elements exist before updating them
|
|
527
|
+
|
|
528
|
+
```javascript
|
|
529
|
+
// Good: Check before updating
|
|
530
|
+
instance.addFrameTask((info) => {
|
|
531
|
+
const el = instance.querySelector('.my-element');
|
|
532
|
+
if (el) {
|
|
533
|
+
el.textContent = `Time: ${info.ownCurrentTimeMs}`;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Bad: Assumes element exists
|
|
538
|
+
instance.addFrameTask((info) => {
|
|
539
|
+
instance.querySelector('.my-element').textContent = `Time: ${info.ownCurrentTimeMs}`;
|
|
540
|
+
});
|
|
541
|
+
```
|
|
542
|
+
<!-- /html-only -->
|
|
543
|
+
<!-- react-only -->
|
|
544
|
+
1. **Use refs to access timegroup** - Don't query the DOM directly from React
|
|
545
|
+
2. **Set initializer in useEffect** - Ensures element is mounted
|
|
546
|
+
3. **Return cleanup functions** - Prevent memory leaks
|
|
547
|
+
4. **Keep initializer fast** - Register callbacks, don't do heavy work
|
|
548
|
+
5. **Use frame tasks for updates** - All time-based logic goes in frame callbacks
|
|
549
|
+
6. **Handle missing elements** - Check if elements exist before updating them
|
|
550
|
+
7. **Avoid React state in frame tasks** - Can cause performance issues; use DOM updates instead
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
// Good: Direct DOM updates in frame task
|
|
554
|
+
instance.addFrameTask((info) => {
|
|
555
|
+
const el = instance.querySelector('.my-element');
|
|
556
|
+
if (el) {
|
|
557
|
+
el.textContent = `Time: ${info.ownCurrentTimeMs}`;
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// Avoid: React state updates in frame task (performance issue)
|
|
562
|
+
instance.addFrameTask((info) => {
|
|
563
|
+
setTime(info.ownCurrentTimeMs); // Called every frame!
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## TypeScript Types
|
|
568
|
+
|
|
569
|
+
```tsx
|
|
570
|
+
import { useRef, useEffect } from "react";
|
|
571
|
+
|
|
572
|
+
interface TimegroupElement extends HTMLElement {
|
|
573
|
+
initializer?: (instance: TimegroupElement) => (() => void) | void;
|
|
574
|
+
addFrameTask: (callback: (info: FrameInfo) => void) => () => void;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
interface FrameInfo {
|
|
578
|
+
ownCurrentTimeMs: number;
|
|
579
|
+
durationMs: number;
|
|
580
|
+
percentComplete: number;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const MyScene = () => {
|
|
584
|
+
const timegroupRef = useRef<TimegroupElement>(null);
|
|
585
|
+
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
const tg = timegroupRef.current;
|
|
588
|
+
if (!tg) return;
|
|
589
|
+
|
|
590
|
+
tg.initializer = (instance: TimegroupElement) => {
|
|
591
|
+
return instance.addFrameTask((info: FrameInfo) => {
|
|
592
|
+
// Typed callback
|
|
593
|
+
});
|
|
594
|
+
};
|
|
595
|
+
}, []);
|
|
596
|
+
|
|
597
|
+
return <Timegroup ref={timegroupRef} mode="fixed" duration="5s" />;
|
|
598
|
+
};
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
## See Also
|
|
602
|
+
|
|
603
|
+
- [timeline-root.md](references/timeline-root.md) - TimelineRoot wrapper (required for React)
|
|
604
|
+
- [hooks.md](references/hooks.md) - useTimingInfo and other React hooks
|
|
605
|
+
- [timegroup.md](references/timegroup.md) - Timegroup component reference
|
|
606
|
+
<!-- /react-only -->
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Sequencing Scenes
|
|
3
|
+
description: Create sequential scene playback using seq-mode timegroups, with automatic cuts and configurable transition timing between scenes.
|
|
4
|
+
type: how-to
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Layout & Timing"
|
|
7
|
+
priority: 11
|
|
8
|
+
related: ["timegroup-modes", "transitions"]
|
|
9
|
+
track: "layout-mastery"
|
|
10
|
+
track_step: 3
|
|
11
|
+
track_title: "Building Sequences"
|
|
12
|
+
prerequisites: ["timegroup", "timegroup-modes"]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Sequencing Scenes
|
|
16
|
+
|
|
17
|
+
Create multi-scene compositions using `mode="sequence"` on a timegroup.
|
|
18
|
+
|
|
19
|
+
## Basic Sequence
|
|
20
|
+
|
|
21
|
+
Children play one after another. Total duration is the sum of all children.
|
|
22
|
+
|
|
23
|
+
```html live
|
|
24
|
+
<ef-timegroup mode="sequence" class="w-[720px] h-[300px] bg-black">
|
|
25
|
+
<ef-timegroup mode="fixed" duration="2s" class="bg-red-500 text-white text-3xl flex items-center justify-center">
|
|
26
|
+
<p>Scene 1</p>
|
|
27
|
+
</ef-timegroup>
|
|
28
|
+
<ef-timegroup mode="fixed" duration="3s" class="bg-blue-500 text-white text-3xl flex items-center justify-center">
|
|
29
|
+
<p>Scene 2</p>
|
|
30
|
+
</ef-timegroup>
|
|
31
|
+
<ef-timegroup mode="fixed" duration="2s" class="bg-green-500 text-white text-3xl flex items-center justify-center">
|
|
32
|
+
<p>Scene 3</p>
|
|
33
|
+
</ef-timegroup>
|
|
34
|
+
</ef-timegroup>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Sequences with Overlap
|
|
38
|
+
|
|
39
|
+
Add `overlap` to create shared time between adjacent items — the foundation for transitions.
|
|
40
|
+
|
|
41
|
+
```html live
|
|
42
|
+
<ef-timegroup mode="sequence" overlap="1s" class="w-[720px] h-[300px] bg-black">
|
|
43
|
+
<ef-timegroup mode="fixed" duration="3s" class="absolute w-full h-full bg-red-500 text-white text-3xl flex items-center justify-center">
|
|
44
|
+
<p>Scene 1</p>
|
|
45
|
+
</ef-timegroup>
|
|
46
|
+
<ef-timegroup mode="fixed" duration="3s" class="absolute w-full h-full bg-blue-500 text-white text-3xl flex items-center justify-center">
|
|
47
|
+
<p>Scene 2</p>
|
|
48
|
+
</ef-timegroup>
|
|
49
|
+
<ef-timegroup mode="fixed" duration="3s" class="absolute w-full h-full bg-green-500 text-white text-3xl flex items-center justify-center">
|
|
50
|
+
<p>Scene 3</p>
|
|
51
|
+
</ef-timegroup>
|
|
52
|
+
</ef-timegroup>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Total duration with overlap: `sum(children) - (overlap × (count - 1))`. Three 3s scenes with 1s overlap = 7s.
|
|
56
|
+
|
|
57
|
+
See [transitions.md](references/transitions.md) for crossfade and slide effects during overlap.
|
|
58
|
+
|
|
59
|
+
## Nested Sequences
|
|
60
|
+
|
|
61
|
+
A sequence can contain other sequences. The inner sequence resolves to a single duration.
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<ef-timegroup mode="sequence" overlap="1s">
|
|
65
|
+
<ef-timegroup mode="sequence">
|
|
66
|
+
<ef-timegroup mode="fixed" duration="3s">Sub A1</ef-timegroup>
|
|
67
|
+
<ef-timegroup mode="fixed" duration="2s">Sub A2</ef-timegroup>
|
|
68
|
+
</ef-timegroup>
|
|
69
|
+
<ef-timegroup mode="fixed" duration="4s">Scene B</ef-timegroup>
|
|
70
|
+
</ef-timegroup>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The inner sequence (5s total) is treated as a single 5s item in the outer sequence.
|
|
74
|
+
|
|
75
|
+
## Mixed Content
|
|
76
|
+
|
|
77
|
+
Sequences work with any element — video, images, text, or combinations:
|
|
78
|
+
|
|
79
|
+
```html live
|
|
80
|
+
<ef-timegroup mode="sequence" class="w-[720px] h-[480px] bg-black">
|
|
81
|
+
<ef-timegroup class="flex flex-col items-center justify-center">
|
|
82
|
+
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4" sourceout="3s" class="absolute top-0 left-0 size-full object-contain z-0"></ef-video>
|
|
83
|
+
<h1 class="relative text-white text-4xl bg-black/50 p-4">Video Scene</h1>
|
|
84
|
+
</ef-timegroup>
|
|
85
|
+
<ef-timegroup mode="fixed" duration="3s" class="bg-slate-900 flex items-center justify-center">
|
|
86
|
+
<h1 class="text-white text-5xl">Text Interlude</h1>
|
|
87
|
+
</ef-timegroup>
|
|
88
|
+
<ef-timegroup class="flex items-center justify-center">
|
|
89
|
+
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4" sourcein="5s" class="absolute top-0 left-0 size-full object-contain z-0"></ef-video>
|
|
90
|
+
<h1 class="relative text-white text-4xl bg-black/50 p-4">Final Scene</h1>
|
|
91
|
+
</ef-timegroup>
|
|
92
|
+
</ef-timegroup>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Background Track Pattern
|
|
96
|
+
|
|
97
|
+
Wrap a sequence with a `contain` parent and add a `fit` sibling for a background that spans the full timeline:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<ef-timegroup mode="contain">
|
|
101
|
+
<ef-timegroup mode="sequence">
|
|
102
|
+
<ef-timegroup mode="fixed" duration="3s">Intro</ef-timegroup>
|
|
103
|
+
<ef-timegroup mode="fixed" duration="10s">Main</ef-timegroup>
|
|
104
|
+
<ef-timegroup mode="fixed" duration="2s">Outro</ef-timegroup>
|
|
105
|
+
</ef-timegroup>
|
|
106
|
+
<ef-audio src="music.mp3" mode="fit"></ef-audio>
|
|
107
|
+
</ef-timegroup>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> **Note:** The `fit` element inherits the 15s total from its `contain` parent, which gets its duration from the `sequence` child.
|
|
111
|
+
|
|
112
|
+
## See Also
|
|
113
|
+
|
|
114
|
+
- [timegroup-modes.md](references/timegroup-modes.md) — mode explanations
|
|
115
|
+
- [transitions.md](references/transitions.md) — crossfade and slide transitions
|
|
116
|
+
- [time-model.md](references/time-model.md) — how time propagates through sequences
|