Scripting
Use addFrameTask to animate elements programmatically on a per-frame basis.
addFrameTask
addFrameTask is an instance method on every ef-timegroup element. Register a callback and it runs on every captured frame.
Signature
timegroupElement.addFrameTask(callback: FrameTaskCallback): () => void
Returns a cleanup function that removes the callback when called.
interface FrameTaskCallback {
(info: {
ownCurrentTimeMs: number; // ms elapsed since this group's own start
currentTimeMs: number; // ms elapsed in the global composition
durationMs: number; // total duration of this group
percentComplete: number; // ownCurrentTimeMs / durationMs, clamped 0–1
element: EFTimegroup; // the timegroup element itself
}): void | Promise<void>;
}
initializer
When rendering, Editframe creates isolated render clones of the root ef-timegroup. Any addFrameTask call in an inline <script> runs once on the live preview but never reaches those clones.
The initializer property solves this. Assign a synchronous callback to it and it runs on both the prime timeline and every render clone. All addFrameTask calls must live inside an initializer.
<ef-timegroup id="root" duration="3s" class="w-[1920px] h-[1080px] bg-black flex items-center justify-center">
<ef-text id="headline" class="text-white text-8xl font-bold" style="opacity: 0;">
Fade In
</ef-text>
</ef-timegroup>
<script type="module">
const root = document.getElementById("root");
root.initializer = (instance) => {
const headline = instance.querySelector("#headline");
instance.addFrameTask(({ percentComplete }) => {
headline.style.opacity = percentComplete;
});
};
</script>
Query elements via the instance argument — not document.getElementById — since render clones are independent documents.
The initializer must be synchronous. Async initializers throw at runtime.
Example: typewriter effect
<ef-timegroup id="root" duration="3s" class="w-[1920px] h-[1080px] bg-black flex items-center justify-center">
<ef-text id="typewriter" class="text-white text-6xl font-mono"></ef-text>
</ef-timegroup>
<script type="module">
const root = document.getElementById("root");
const fullText = "Hello, world.";
root.initializer = (instance) => {
const el = instance.querySelector("#typewriter");
instance.addFrameTask(({ percentComplete }) => {
el.textContent = fullText.slice(0, Math.floor(percentComplete * fullText.length));
});
};
</script>
Example: counter
<ef-timegroup id="root" duration="2s" class="w-[1920px] h-[1080px] bg-black flex items-center justify-center">
<ef-text id="counter" class="text-white text-9xl font-bold tabular-nums">0</ef-text>
</ef-timegroup>
<script type="module">
const root = document.getElementById("root");
const target = 1000000;
function easeInOut(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
root.initializer = (instance) => {
const counter = instance.querySelector("#counter");
instance.addFrameTask(({ percentComplete }) => {
counter.textContent = Math.round(easeInOut(percentComplete) * target).toLocaleString();
});
};
</script>
Targeting individual elements
ownCurrentTimeMs is always relative to the group's own start time, not the global composition clock. A group starting at 2s into the composition reports ownCurrentTimeMs = 0 at that moment. Use currentTimeMs if you need the global clock.
React
In React, pass setup logic as a function prop — there is no initializer property to set directly. Use a useEffect that runs once after the element connects:
import { useRef, useEffect } from "react";
import { Timegroup, Text } from "@editframe/react";
import type { EFTimegroup } from "@editframe/elements";
function FadeIn() {
const rootRef = useRef<EFTimegroup>(null);
useEffect(() => {
const root = rootRef.current;
if (!root) return;
root.initializer = (instance) => {
const headline = instance.querySelector<HTMLElement>("#headline");
instance.addFrameTask(({ percentComplete }) => {
if (headline) headline.style.opacity = String(percentComplete);
});
};
}, []);
return (
<Timegroup ref={rootRef} duration="3s" className="w-[1920px] h-[1080px] bg-black flex items-center justify-center">
<Text id="headline" className="text-white text-8xl font-bold" style={{ opacity: 0 }}>
Fade In
</Text>
</Timegroup>
);
}