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
290 lines
13 KiB
Plaintext
290 lines
13 KiB
Plaintext
---
|
||
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>
|