按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。
Optimize Web Animations
Core Rule
Measure the real page before editing. The goal is not to remove motion; it is to make offscreen work stop, visible motion resume correctly, and route/unmount cleanup release long-lived resources.
Use Codex Browser when available, especially for localhost pages. Do not use Chrome unless the user explicitly asks for it.
Workflow
- Inspect repo context.
- Read
AGENTS.mdor local instructions. - Run
git status --shortearly. - Find page components, animation hooks, CSS keyframes,
requestAnimationFrame,setInterval,setTimeout, canvas/WebGL/physics components, media elements, GSAP timelines/tweens, and existing visibility utilities. - Search effect cleanup for event listeners, observers, RAF loops, intervals, timers, external scripts, media streams, WebGL textures/materials/geometries/renderers, and async work that can complete after unmount.
- If the worktree is dirty, plan narrow staging from the start.
- Capture a baseline in the browser.
- Open the exact route the user named.
- Profile at top, mid-page, footer/lower content, and one mobile viewport when layout could differ.
- Count CSS animations by computed
animationName,animationPlayState, and visibility. Include::beforeand::after. - Inspect canvases/WebGL elements separately; CSS profiling does not prove RAF loops have stopped.
- Record which animation names are running offscreen and the DOM owners responsible.
- For memory/leak asks, also record element/canvas/image/iframe counts, exposed JS heap metrics when available, an idle sample after 10-30 seconds, and a short route-cycle sample. If heap APIs return
nullor the Browser sandbox blocks monkey-patching, say so and rely on stable observable counts plus source audit. - Keep stress tests bounded. A Browser tab crash during profiling is evidence of overload, but do not over-attribute the cause unless reproduced by a minimal test.
- See
references/browser-profiling.mdfor a reusable Codex Browser evaluator.
- Patch the smallest owner that controls the motion.
- Prefer an existing page reveal/visibility hook if the app has one.
- Otherwise add an
IntersectionObserverthat toggles a stable class such asis-offscreenon sections and animated child elements. - Pause CSS animations with targeted rules:
main > section.is-offscreen .expensive-animation,
.expensive-animation.is-offscreen {
animation-play-state: paused !important;
}- For repeated cards or placeholders, observe the card shell and the animated descendants, not the whole document.
- For marquee/ticker tracks, pause the track when its section is offscreen.
- For skeleton loaders and pseudo-element glimmers, include
::beforeand::afterpause selectors where needed. - For canvas/WebGL/physics loops, gate the RAF loop directly:
- Start when the canvas/container intersects.
- Cancel
requestAnimationFramewhen offscreen. - Resume on re-entry.
- Disconnect observers and cancel frames on cleanup.
- Add a non-visual debug marker such as
data-animation-activewhen it helps browser verification. - Respect
prefers-reduced-motionif the component already does, and avoid introducing React render loops for scroll/animation state. - For leak hardening:
- Clear every timeout/interval created by the effect.
- Cancel RAF before unmount and before restarting a loop.
- Disconnect
IntersectionObserver,ResizeObserver,MutationObserver, and custom subscriptions. - Remove global/window/document listeners with the same handler reference.
- Dispose Three/WebGL textures, materials, geometries, renderers, and remove renderer DOM nodes.
- Kill GSAP tweens/timelines for DOM nodes and mutable objects such as shader uniforms.
- Stop media streams and pause detached video/audio sources.
- Guard async loaders with an
isDisposedflag and dispose loaded resources if they resolve after unmount. - In React cleanup, capture
ref.currentvalues inside the effect before returning cleanup if lint warns the ref may change. - Cap physics or simulation frame deltas after visibility pauses so delayed frames do not run oversized updates.
- Verify behavior, not just builds.
- Reload the route and rerun the same top/mid/footer/mobile profiles.
- Target result:
offscreenRunningCount: 0for the page sections under test. - Confirm visible animations still run or resume when scrolled into view.
- Confirm RAF/canvas loops report inactive offscreen and active in view, or otherwise prove cancellation from source/runtime state.
- For leak audits, compare before/after route cycles and idle samples. DOM/canvas/image counts should return to the same baseline after repeated navigation, allowing for small expected async content changes.
- Exercise a normal page interaction such as search/filter/navigation so the observer does not break dynamic content.
- Check fresh-tab console warnings/errors.
- Run local checks.
- Use the repo's normal gates. For React/Vite apps this is often:
git diff --check
npm run lint
npm run build- Mention known non-fatal warnings separately from failures.
- Commit narrowly when requested by repo/user instructions.
- If unrelated dirty changes exist, use an isolated index:
rm -f /tmp/<task>-index
GIT_INDEX_FILE=/tmp/<task>-index git read-tree HEAD
# Apply only the intended hunks to the temporary index.
GIT_INDEX_FILE=/tmp/<task>-index git diff --cached --check
GIT_INDEX_FILE=/tmp/<task>-index git commit -m "Pause offscreen <page> animations"
git restore --staged <files> 2>/dev/null || true- Never stage broad files from a dirty worktree unless every hunk belongs to the task.
- Report with evidence.
- Lead with findings: what was still running, what looked leak-prone, and what could not be measured.
- Separate source-audit risks from live Browser measurements.
- Include the exact sampled route(s), offscreen animation counts, DOM/canvas count stability, route-cycle result, and local checks.
- State limitations plainly, especially unavailable heap counters or blocked Browser instrumentation.
Good Fix Patterns
- Section-level
is-offscreenplus element-levelis-offscreenfor long sections where below-the-fold child animations can still run. - Shared visibility selector constants per route, such as
COURSES_PAGE_ANIMATION_VISIBILITY_SELECTOR. IntersectionObserverthresholds around0.01for animation gating.- Direct RAF loop control for WebGL/canvas effects; CSS
animation-play-statecannot pause JavaScript render loops. - Frame delta caps for physics loops that resume after a paused or delayed frame.
- Captured cleanup nodes for React refs used by GSAP/WebGL effects.
isDisposedguards for image/video/texture/data loaders that may resolve after unmount.- Short idle and route-cycle probes to catch accumulating DOM nodes, canvases, iframes, or unreleased media.
Avoid
- Removing all animations to make the profile pass.
- Pausing visible hero motion because an ancestor selector is too broad.
- Assuming
animation-play-statecovers pseudo-elements or JavaScript RAF loops. - Trusting a single top-of-page measurement on long pages.
- Treating unavailable heap counters as proof there is no memory leak.
- Running unbounded stress loops in the Browser; use bounded cycles and record crashes without overstating causality.
- Using screenshots alone as performance proof.
- Letting unrelated local hunks ride along in the commit.

