ef-timegroup is the core timing primitive. Every composition has at least one — the root group that defines the total video duration. Groups nest to build complex timelines.
duration
HTML timing attributes accept time strings such as 5s, 500ms, and 2m; a bare number such as "2.5" means seconds. JavaScript timing APIs such as currentTime, duration, offset, and seek() use seconds. React's duration and offset props also accept numeric seconds. Fields explicitly suffixed Ms use milliseconds.
Media speed and source time
For ef-video and ef-audio, sourcein, sourceout, trimstart, and trimend refer to seconds in the original media file. playback-rate specifies source seconds per composition second and preserves audio pitch; it accepts finite values from 0.25 to 4, with a default of 1.
Without an explicit duration, the clip's duration is its selected source span divided by the rate. For example, sourcein="2s" sourceout="10s" playback-rate="2" produces a four-second clip. At one composition second into that clip, the source is at four seconds.
An explicit duration stays in composition seconds and takes precedence over sourceout: sourcein="2s" duration="3s" playback-rate="2" uses source seconds 2 through 8. The end is clamped to the known file length, which can shorten the resulting clip.
mode="sequence"
Children play one after another. Without overlap, the group's total duration is the sum of its children's durations. Inactive scenes are hidden; seeking to the end holds the last scene's final frame.
<ef-timegroup mode="sequence" id="root">
<ef-timegroup mode="fixed" duration="2s" class="bg-indigo-900">
<p>Scene 1 — 2s</p>
</ef-timegroup>
<ef-timegroup mode="fixed" duration="3s" class="bg-rose-900">
<p>Scene 2 — 3s</p>
</ef-timegroup>
<ef-timegroup mode="fixed" duration="2s" class="bg-emerald-900">
<p>Scene 3 — 2s</p>
</ef-timegroup>
</ef-timegroup>
<style>
#root {
width: 1920px;
height: 1080px;
background-color: black;
> ef-timegroup {
display: grid;
place-items: center;
color: white;
font-size: 6rem;
}
}
</style>Sequence with overlapping transitions
Set overlap on a mode="sequence" to start each scene before the previous one ends. Both scenes are active during the shared window. Overlap shortens the total duration: the 2s, 3s, and 2s scenes below make a 5s sequence with 1s overlap.
In order to use the overlap window for a transition, add CSS animations that reference the special timing variables --ef-transition-duration and --ef-transition-out-start.
To animate a transition into a scene, use --ef-transition-duration as the animation duration and start at local 0s. To animate a scene out, use the same duration but start at --ef-transition-out-start, which is that scene's duration minus the overlap.
<ef-timegroup mode="sequence" id="transitions" overlap="1s">
<ef-timegroup mode="fixed" duration="2s" class="bg-indigo-900">
<p>Scene 1 — 2s</p>
</ef-timegroup>
<ef-timegroup mode="fixed" duration="3s" class="bg-rose-900">
<p>Scene 2 — 3s</p>
</ef-timegroup>
<ef-timegroup mode="fixed" duration="2s" class="bg-emerald-900">
<p>Scene 3 — 2s</p>
</ef-timegroup>
</ef-timegroup>
<style>
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slide-out {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
#transitions {
width: 1920px;
height: 1080px;
background-color: black;
> ef-timegroup {
display: grid;
place-items: center;
color: white;
font-size: 6rem;
animation:
/* Fade in animation */
fade-in var(--ef-transition-duration) ease-out both,
/* Slide out animation */
slide-out var(--ef-transition-duration) ease-in var(--ef-transition-out-start) both;
}
}
</style>mode="fixed"
Children share a timeline whose length is the group's explicit duration. Each child's local clock starts at its optional offset within the group.
Unlike sequence scenes, children in fixed, contain, and fit groups remain visible outside their own playback interval: visuals hold their first frame before the offset and their last frame after the clip ends. Audio is silent outside the interval. Use a sequence or timeline-driven visibility when an overlay must disappear.
<ef-timegroup mode="fixed" duration="6s" class="w-[1920px] h-[1080px] bg-black">
<ef-video
src="https://assets.editframe.com/bars-n-tone.mp4"
duration="6s"
class="absolute inset-0 size-full"
>
</ef-video>
<ef-timegroup
mode="fixed"
duration="3s"
offset="0.5s"
class="absolute top-16 left-16 w-1/5 h-1/5 text-white text-5xl font-bold bg-black/50 px-4 py-2"
>
<p>local clock starts at 0.5s</p>
</ef-timegroup>
<ef-timegroup
mode="fixed"
duration="3s"
offset="3s"
class="absolute bottom-32 left-16 w-1/5 h-1/5 text-yellow-300 text-5xl font-bold bg-black/50 px-4 py-2"
>
<p>local clock starts at 3s</p>
</ef-timegroup>
</ef-timegroup>offset shifts when an element begins within its parent fixed group.
mode="contain"
The group's duration reaches the furthest child end: the maximum of each child's offset + duration. Below, Scene B starts at 2s and lasts 6s, so the group lasts 8s.
<ef-timegroup mode="contain" class="w-[1920px] h-[1080px]">
<ef-timegroup mode="fixed" duration="4s" class="absolute right-0 w-half h-full bg-indigo-900 flex items-center justify-center">
<p class="text-white text-7xl font-bold">Scene A</p>
</ef-timegroup>
<ef-timegroup mode="fixed" duration="6s" offset="2s" class="absolute left-0 w-half h-full bg-rose-900 flex items-center justify-center">
<p class="text-white text-7xl font-bold">Scene B</p>
</ef-timegroup>
</ef-timegroup>mode="fit"
The group's duration matches its parent's duration. Use it for a layer whose timeline should span the parent composition.
fit does not change a media file's playback rate, loop it, or extend its sound past the available source. Set playback-rate explicitly when you want to retime media.
This can be combined with a sequence to give a background audio layer the full composition's timeline:
<ef-timegroup mode="contain" class="w-[1920px] h-[1080px]">
<ef-timegroup mode="fit">
<ef-audio src="https://assets.editframe.com/muted-guitar.mp3"></ef-audio>
</ef-timegroup>
<ef-timegroup mode="sequence">
<ef-video
src="https://assets.editframe.com/departure-prep.mp4"
sourceout="4s"
>
</ef-video>
<ef-video
src="https://assets.editframe.com/window-seat.mp4"
sourceout="4s"
>
</ef-video>
<ef-video
src="https://assets.editframe.com/landing.mp4"
sourcein="8s"
sourceout="12s"
>
</ef-video>
</ef-timegroup>
</ef-timegroup>Timegroups vs Temporal Elements
Timegroups are the typical element to use to structure the timing of your composition, but they aren't the only temporal elements.
<ef-video><ef-audio><ef-image><ef-text>
These can be direct children of a timegroup to simplify your composition structure.
Wrap them in a timegroup when a scene also needs ordinary HTML content, graphics, or shared layout.
loop
Add loop to the root ef-timegroup to loop the composition in the browser preview:
<ef-timegroup duration="5s" loop class="w-[1920px] h-[1080px] bg-black">
...
</ef-timegroup>loop has no effect during rendering — the renderer plays through exactly once.
fps
fps controls the frame rate used when scrubbing and rendering. It defaults to 30. Drag the slider to see how frame rate affects animation smoothness:
<ef-timegroup id="root" mode="contain">
<!-- The FPS for this timegroup is controlled by the slider -->
<ef-timegroup mode="fixed" duration="6s" fps="4">
<p>FPS: 4</p>
</ef-timegroup>
<!-- The FPS for this timegroup is fixed at 30 -->
<ef-timegroup mode="fixed" duration="6s" fps="30">
<p>FPS: 30</p>
</ef-timegroup>
<!--
Each timegroup can have it's own FPS value.
The output FPS will be the FPS of the ROOT timegroup.
This allows you to have different apparent FPS for different
parts of the composition.
-->
</ef-timegroup>
<style>
#root {
width: 1920px;
height: 1080px;
background-color: #888888;
display: flex;
flex-direction: column;
> ef-timegroup {
height: 50%;
position: static;
display: grid;
place-items: center;
}
}
/*
* In order to demonstrate the FPS, we need to apply an animation.
* In Editframe, CSS animations are automatically syncronized to
* the composition's timeline. This means that at 4 fps, the animation
* will update every 250ms, and at 30 fps, it will update every ~33ms.
*/
@keyframes slide {
0% { transform: translateX(-25%); }
50% { transform: translateX(25%); }
100% { transform: translateX(-25%); }
}
p {
animation: slide calc(var(--ef-duration) / 2) linear infinite;
font-size: 200px;
font-weight: bold;
color: white;
}
</style>