Scripting
Add dynamic JavaScript behavior to timegroups.
Initializer
Set up behavior that runs once per instance (prime timeline and render clones).
Basic Usage
<ef-timegroup id="my-scene" mode="fixed" duration="5s"><div class="content"></div></ef-timegroup><script>const tg = document.querySelector('#my-scene');tg.initializer = (instance) => {// Runs once on prime timeline, once on each render cloneconsole.log('Initializer running');};</script>
Constraints
-
Must be synchronous - No async/await, no Promise return
-
Must complete quickly - Less than 100ms (error thrown) or less than 10ms (warning logged)
-
Register callbacks only - Don't do expensive work in initializer
Timing
- Set before connection: Runs after element connects to DOM
- Set after connection: Runs immediately
- Clones: Automatically copy and run initializer
Frame Tasks
Register callbacks that execute on each frame during rendering.
addFrameTask()
const tg = document.querySelector('ef-timegroup');tg.initializer = (instance) => {const cleanup = instance.addFrameTask((info) => {// Called on each frame// info contains: ownCurrentTimeMs, durationMs, percentComplete, etc.});// cleanup() removes the callback when called};
Callback Info
Frame task callbacks receive timing information:
instance.addFrameTask((info) => {console.log(info.ownCurrentTimeMs); // Current time in msconsole.log(info.durationMs); // Total durationconsole.log(info.percentComplete); // 0-1 progress});
Multiple Callbacks
Register multiple frame tasks - they execute in parallel:
tg.initializer = (instance) => {instance.addFrameTask((info) => {// Update text});instance.addFrameTask((info) => {// Update position});};
Examples
Dynamic Text Updates
<ef-timegroup id="counter" mode="fixed" duration="10s"><div class="text-4xl text-white counter-text"></div></ef-timegroup><script>const tg = document.querySelector('#counter');tg.initializer = (instance) => {instance.addFrameTask((info) => {const text = instance.querySelector('.counter-text');const seconds = (info.ownCurrentTimeMs / 1000).toFixed(2);text.textContent = `Time: ${seconds}s`;});};</script>
Procedural Animation
<ef-timegroup id="animated" mode="fixed" duration="5s"><div class="box"></div></ef-timegroup><script>const tg = document.querySelector('#animated');tg.initializer = (instance) => {instance.addFrameTask((info) => {const box = instance.querySelector('.box');const progress = info.percentComplete;// Move box across screenbox.style.transform = `translateX(${progress * 500}px)`;// Rotate based on timeconst rotation = (info.ownCurrentTimeMs / 10) % 360;box.style.transform += ` rotate(${rotation}deg)`;});};</script>
Data-Driven Content
<ef-timegroup id="data-scene" mode="fixed" duration="8s"><div class="data-display"></div></ef-timegroup><script>const data = [{ time: 0, value: 10 },{ time: 2000, value: 25 },{ time: 4000, value: 40 },{ time: 6000, value: 60 },];const tg = document.querySelector('#data-scene');tg.initializer = (instance) => {instance.addFrameTask((info) => {const display = instance.querySelector('.data-display');// Find current data pointconst current = data.find((d, i) => {const next = data[i + 1];return info.ownCurrentTimeMs >= d.time &&(!next || info.ownCurrentTimeMs < next.time);});if (current) {display.textContent = `Value: ${current.value}`;}});};</script>
Cleanup Pattern
tg.initializer = (instance) => {// Set up resourcesconst state = { count: 0 };const cleanup = instance.addFrameTask((info) => {state.count++;console.log(`Frame ${state.count}`);});// Cleanup is automatic when instance is removed// But you can manually cleanup if needed:// cleanup();};
Prime Timeline vs Render Clone
The initializer runs on both:
- Prime timeline: Interactive preview in browser
- Render clone: Headless rendering for video export
Same code runs in both contexts, ensuring consistent behavior.
tg.initializer = (instance) => {// This code runs identically on prime timeline and render clonesinstance.addFrameTask((info) => {// Update content based on time});};
Best Practices
- Keep initializer fast - Register callbacks, don't do heavy work
- Use frame tasks for updates - All time-based logic goes in frame callbacks
- Avoid side effects - Don't modify external state, keep logic contained
- Test in both contexts - Preview in browser AND render to video
- Handle missing elements - Check if elements exist before updating them
// Good: Check before updatinginstance.addFrameTask((info) => {const el = instance.querySelector('.my-element');if (el) {el.textContent = `Time: ${info.ownCurrentTimeMs}`;}});// Bad: Assumes element existsinstance.addFrameTask((info) => {instance.querySelector('.my-element').textContent = `Time: ${info.ownCurrentTimeMs}`;});