Skip to main content
These are mistakes that cannot be caught by the linter. For automated checks, run npx hyperframes lint (see CLI).
The first two mistakes — animating video element dimensions and controlling media playback in scripts — are the most common causes of broken compositions. If your video looks wrong, check these first.
Symptom: Video frames stop updating, or browser performance drops severely.Cause: GSAP animating width, height, top, left directly on a <video> element can cause the browser to stop rendering frames.Before (broken):
index.html
// Animating the video element directly — causes frame rendering to stop
tl.to("#el-video", { width: 500, height: 280, top: 700, left: 1400 }, 26);
After (fixed):
index.html
<!-- Wrap the video in a div and animate the wrapper -->
<div id="pip-wrapper" style="position: absolute; width: 1920px; height: 1080px;">
  <video id="el-video" data-start="0" data-track-index="0"
         src="./assets/video.mp4" style="width: 100%; height: 100%;"></video>
</div>
index.html
// Animate the wrapper — the video fills it at 100%
tl.to("#pip-wrapper", { width: 500, height: 280, top: 700, left: 1400 }, 26);
Use a non-timed wrapper <div> for visual effects like picture-in-picture. Animate the wrapper; let the video fill it via CSS.
Symptom: Audio/video playback is out of sync, or plays when it should not.Cause: Calling video.play(), video.pause(), or setting audio.currentTime in your scripts. The framework owns all media playback.Before (broken):
index.html
// Conflicts with framework media sync
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;
After (fixed):
index.html
// Don't control media playback at all. The framework handles it.
// Use GSAP for visual animations only:
tl.to("#el-video", { opacity: 1, duration: 0.5 }, 0);
The framework reads data-start, data-media-start, and data-volume to control when and how media plays. See Compositions: Two Layers for the separation between HTML primitives and scripts.
Symptom: Video plays for a few seconds then stops. Timeline shows 8-10 seconds even though the video is minutes long.Cause: The composition duration equals the GSAP timeline duration, not data-duration on the video. If your last GSAP animation ends at 8 seconds, the composition is 8 seconds long — regardless of how long the video source is.Before (broken):
index.html
// Timeline is only 7.8s long — video cuts off after 7.8 seconds
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
After (fixed):
index.html
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);

// Extend the timeline to 283 seconds to match the video length
tl.set({}, {}, 283);
tl.set({}, {}, TIME) adds a zero-duration tween at the specified time, extending the timeline without affecting any elements.
A quick check: run npx hyperframes compositions to see the resolved duration of each composition. If it is shorter than expected, your timeline needs extending.
Symptom: Elements are always visible, ignoring their data-start and data-duration timing.Cause: The class="clip" attribute tells the runtime to manage the element’s visibility lifecycle. Without it, the element is always rendered.Before (broken):
index.html
<!-- Missing class="clip" — this element is always visible -->
<h1 id="title" data-start="2" data-duration="5" data-track-index="0">
  Hello World
</h1>
After (fixed):
index.html
<!-- With class="clip", the runtime shows this only from 2s to 7s -->
<h1 id="title" class="clip" data-start="2" data-duration="5" data-track-index="0">
  Hello World
</h1>
The linter catches this one: npx hyperframes lint will flag timed elements missing class="clip".
Symptom: Animations don’t play. The composition appears static.Cause: The key used in window.__timelines must exactly match the data-composition-id attribute on the composition root element.Before (broken):
index.html
// Mismatch: HTML says "my-video", script registers "root"
// <div data-composition-id="my-video" ...>
window.__timelines["root"] = tl;
After (fixed):
index.html
// Key matches the data-composition-id attribute
// <div data-composition-id="my-video" ...>
window.__timelines["my-video"] = tl;

Debugging Checklist

When something does not work, check in this order:
  1. Run the linter: npx hyperframes lint — catches most structural issues
  2. Timeline registered? Is window.__timelines["<id>"] set? Does the key match data-composition-id?
  3. GSAP-only animations? Only animate visual properties (opacity, transform, color) — see GSAP Animation
  4. Timeline long enough? Add tl.set({}, {}, DURATION) at the end — see Timeline Duration
  5. Console errors? Open browser console — runtime errors show as [Browser:ERROR]
  6. Still stuck? See Troubleshooting for environment and rendering issues

Next Steps

Troubleshooting

Fix environment and rendering issues

GSAP Animation

Review animation rules and patterns

HTML Schema Reference

Full attribute reference and checklist

Data Attributes

Timing, media, and composition attributes