Files
wehub-resource-sync 85453da49f
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Docs / Validate docs (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Sync skills to ClawHub / Publish changed skills (push) Waiting to run
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:58:35 +08:00

290 lines
13 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Common Mistakes
description: "Pitfalls that break Hyperframes compositions."
---
These are mistakes that cannot be caught by the linter. For automated checks, run `npx hyperframes lint` (see [CLI](/packages/cli#lint)).
<Warning>
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.
</Warning>
<AccordionGroup>
<Accordion title="Animating video element dimensions">
**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):**
```javascript 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):**
```html 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>
```
```javascript 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.
</Accordion>
<Accordion title="Controlling media playback in scripts">
**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](/reference/html-schema#framework-managed-behavior).
**Before (broken):**
```javascript index.html
// Conflicts with framework media sync
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;
```
**After (fixed):**
```javascript 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`](/concepts/data-attributes#timing-attributes), [`data-media-start`](/concepts/data-attributes#media-attributes), and [`data-volume`](/concepts/data-attributes#media-attributes) to control when and how media plays. See [Compositions: Two Layers](/concepts/compositions#two-layers-primitives-and-scripts) for the separation between HTML primitives and scripts.
</Accordion>
<Accordion title="Composition duration shorter than video">
**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](/guides/gsap-animation#timeline-duration-and-composition-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):**
```javascript 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):**
```javascript 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.
<Tip>
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.
</Tip>
</Accordion>
<Accordion title="Setting the root render length from a script or --variables does nothing">
**Symptom:** You set the **root** composition's `data-duration` from a script (or expose it as a variable and pass `--variables`), but the render length never changes. It stays locked at whatever `data-duration` is written in the source HTML.
**Cause:** The root composition's `data-duration` (the total render length / frame count) is read **once at compile time**, before your scripts run, exactly like `data-width` / `data-height`. A static root `data-duration` is locked at that point, so a later `root.setAttribute("data-duration", ...)` or a `--variables`-driven value is ignored. This is unlike a **clip's** `data-duration`, which the renderer re-reads from the live DOM after scripts run.
**Before (broken):**
```html index.html
<div id="root" data-composition-id="main" data-duration="2" data-width="1920" data-height="1080">
...
</div>
<script>
// Ignored: the root render length was already fixed at 2s at compile time.
document.getElementById("root").setAttribute("data-duration", "5");
</script>
```
**After (fixed):** author the root `data-duration` directly, one value per output. To produce a different length, author (or generate) a variant with that root `data-duration`. See [What can't be a variable](/concepts/variables#what-cant-be-a-variable).
<Tip>
Only the root composition's *total length* is compile-time-fixed. A clip's own `data-duration` (and a `<video>`/`<audio>` trim length) is re-read from the live DOM, so scripts and variables can still drive those, see [Swapping media: do you need to vary duration too?](/concepts/variables#swapping-media-do-you-need-to-vary-duration-too).
</Tip>
</Accordion>
<Accordion title="Missing class='clip' on timed elements">
**Symptom:** Elements are always visible, ignoring their `data-start` and `data-duration` timing.
**Cause:** The [`class="clip"`](/concepts/data-attributes#element-visibility) attribute tells the runtime to manage the element's visibility lifecycle. Without it, the element is always rendered.
**Before (broken):**
```html 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):**
```html 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>
```
<Note>
The linter catches this one: `npx hyperframes lint` will flag timed elements missing `class="clip"`.
</Note>
</Accordion>
<Accordion title="Oversized source images">
**Symptom:** Preview stutters during scenes with images on screen. Render is slower than expected.
**Cause:** Source images at much higher resolution than the canvas. Chrome decodes images to raw RGBA bitmaps before displaying them, and bitmap size is `width × height × 4` bytes — independent of file size on disk. A 7000×5000 JPEG is 140MB decoded, even if the file is only 2MB.
Displaying such an image in a 384×1080 region wastes memory and forces the compositor to resample a huge texture every frame.
**Before (bloated):**
```html index.html
<!-- 7000x5000 source, ~140MB decoded -->
<img class="clip" data-start="0" data-duration="3"
src="./assets/hero-scene.jpg" />
```
**After (sized to the canvas):**
```bash Terminal
# Resize a batch of images to fit within 3840x3840, preserving aspect ratio
mkdir -p assets/resized
mogrify -path assets/resized -resize 3840x3840\> assets/*.jpg
```
```html index.html
<!-- ~3840x2560 source, ~40MB decoded -->
<img class="clip" data-start="0" data-duration="3"
src="./assets/resized/hero-scene.jpg" />
```
**Rule of thumb:** source images at most 2x the canvas dimensions. For a 1920×1080 composition, 3840×2160 is already plenty. See [Performance: Image sizing](/guides/performance#image-sizing).
</Accordion>
<Accordion title="Heavy backdrop-filter stacks">
**Symptom:** Specific scenes drop to 5-10fps in preview. The composition is fine elsewhere.
**Cause:** `backdrop-filter: blur()` on large elements, especially stacked at high radii. Each blur layer forces the compositor to sample pixels behind the element, run a blur kernel, and composite the result. Stacked layers multiply the cost.
**Before (expensive):**
```css
/* 8 layers per side = 16 blur passes every frame */
.pb-1 { backdrop-filter: blur(1px); }
.pb-2 { backdrop-filter: blur(2px); }
.pb-3 { backdrop-filter: blur(4px); }
.pb-4 { backdrop-filter: blur(8px); }
.pb-5 { backdrop-filter: blur(16px); }
.pb-6 { backdrop-filter: blur(32px); }
.pb-7 { backdrop-filter: blur(64px); }
.pb-8 { backdrop-filter: blur(128px); }
```
**After (3 tuned layers):**
```css
/* Fewer passes with hand-picked radii — visually similar, much cheaper */
.pb-1 { backdrop-filter: blur(4px); }
.pb-2 { backdrop-filter: blur(16px); }
.pb-3 { backdrop-filter: blur(48px); }
```
**Guidelines:**
- Keep stacked `backdrop-filter` layers to 2-3 per region
- Avoid radii above 64px over large areas — the biggest radii dominate the total cost
- For a static blur effect, pre-render it into a PNG once and overlay with a regular `<img>`
See [Performance: backdrop-filter: blur()](/guides/performance#backdrop-filter-blur) for the full breakdown.
</Accordion>
<Accordion title="Expected HDR output but got SDR">
**Symptom:** Expected an HDR render, but the output looks the same as SDR or `ffprobe` reports `color_transfer=bt709`.
**Cause:** By default, Hyperframes only switches to HDR encoding when a source `<video>` or `<img>` is tagged with BT.2020 / PQ / HLG color metadata. Common reasons HDR is not engaged:
1. **All sources are SDR.** Auto-detect leaves SDR-only compositions in SDR. Verify with `ffprobe`:
```bash Terminal
ffprobe -v error -show_streams source.mp4 | grep color_transfer
# Want: smpte2084 (PQ) or arib-std-b67 (HLG)
# SDR: bt709, smpte170m, bt470bg, etc.
```
2. **Wrong output format.** HDR output requires MP4. `--format mov` and `--format webm` fall back to SDR — Hyperframes logs a warning when this happens.
3. **SDR was forced.** `--sdr` disables HDR even when HDR sources are present.
If you need HDR regardless of source metadata, use `--hdr` to force it.
`--docker` works the same as local rendering — auto-detect, `--hdr`, and `--sdr` are all forwarded into the container and produce the same output decisions (slower, since the container falls back to software WebGL for SDR DOM capture).
See [HDR Rendering](/guides/hdr) for the full source requirements and verification steps.
</Accordion>
<Accordion title="Timeline key doesn't match data-composition-id">
**Symptom:** Animations don't play. The composition appears static.
**Cause:** The key used in `window.__timelines` must exactly match the [`data-composition-id`](/concepts/data-attributes#composition-attributes) attribute on the composition root element.
**Before (broken):**
```javascript index.html
// Mismatch: HTML says "my-video", script registers "root"
// <div data-composition-id="my-video" ...>
window.__timelines["root"] = tl;
```
**After (fixed):**
```javascript index.html
// Key matches the data-composition-id attribute
// <div data-composition-id="my-video" ...>
window.__timelines["my-video"] = tl;
```
</Accordion>
</AccordionGroup>
## 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`](/concepts/data-attributes#composition-attributes)?
3. **GSAP-only animations?** Only animate visual properties (opacity, transform, color) — see [GSAP Animation](/guides/gsap-animation#key-rules)
4. **Timeline long enough?** Add `tl.set({}, {}, DURATION)` at the end — see [Timeline Duration](/guides/gsap-animation#timeline-duration-and-composition-duration)
5. **Console errors?** Open browser console — runtime errors show as `[Browser:ERROR]`
6. **Still stuck?** See [Troubleshooting](/guides/troubleshooting) for environment and rendering issues
## Next Steps
<CardGroup cols={2}>
<Card title="Troubleshooting" icon="wrench" href="/guides/troubleshooting">
Fix environment and rendering issues
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Review animation rules and patterns
</Card>
<Card title="HTML Schema Reference" icon="code" href="/reference/html-schema">
Full attribute reference and checklist
</Card>
<Card title="Data Attributes" icon="database" href="/concepts/data-attributes">
Timing, media, and composition attributes
</Card>
</CardGroup>