optimize-web-animations

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Optimize Web Animations

优化Web动画

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.
在编辑前先测量真实页面性能。目标并非移除动画,而是让屏幕外的动画停止运行、可视区域的动画正确恢复,以及在路由切换/组件卸载时清理释放长期占用的资源。
若可用,优先使用Codex Browser,尤其是针对本地localhost页面。除非用户明确要求,否则不要使用Chrome。

Workflow

工作流程

  1. Inspect repo context.
    • Read
      AGENTS.md
      or local instructions.
    • Run
      git status --short
      early.
    • 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.
  2. 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
      ::before
      and
      ::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
      null
      or 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.md
      for a reusable Codex Browser evaluator.
  3. Patch the smallest owner that controls the motion.
    • Prefer an existing page reveal/visibility hook if the app has one.
    • Otherwise add an
      IntersectionObserver
      that toggles a stable class such as
      is-offscreen
      on sections and animated child elements.
    • Pause CSS animations with targeted rules:
css
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
    ::before
    and
    ::after
    pause selectors where needed.
  • For canvas/WebGL/physics loops, gate the RAF loop directly:
    • Start when the canvas/container intersects.
    • Cancel
      requestAnimationFrame
      when offscreen.
    • Resume on re-entry.
    • Disconnect observers and cancel frames on cleanup.
    • Add a non-visual debug marker such as
      data-animation-active
      when it helps browser verification.
  • Respect
    prefers-reduced-motion
    if 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
      isDisposed
      flag and dispose loaded resources if they resolve after unmount.
    • In React cleanup, capture
      ref.current
      values 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.
  1. Verify behavior, not just builds.
    • Reload the route and rerun the same top/mid/footer/mobile profiles.
    • Target result:
      offscreenRunningCount: 0
      for 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.
  2. Run local checks.
    • Use the repo's normal gates. For React/Vite apps this is often:
bash
git diff --check
npm run lint
npm run build
  • Mention known non-fatal warnings separately from failures.
  1. Commit narrowly when requested by repo/user instructions.
    • If unrelated dirty changes exist, use an isolated index:
bash
rm -f /tmp/<task>-index
GIT_INDEX_FILE=/tmp/<task>-index git read-tree HEAD
  1. 检查仓库上下文。
    • 阅读
      AGENTS.md
      或本地说明文档。
    • 尽早执行
      git status --short
      命令。
    • 查找页面组件、动画钩子、CSS关键帧、
      requestAnimationFrame
      setInterval
      setTimeout
      、Canvas/WebGL/物理组件、媒体元素、GSAP时间线/补间动画,以及已有的可见性工具。
    • 检查事件监听器、观察者、RAF循环、定时器、外部脚本、媒体流、WebGL纹理/材质/几何体/渲染器,以及可能在组件卸载后仍会完成的异步任务的清理逻辑。
    • 如果工作区存在未提交的修改,从一开始就规划好范围狭窄的暂存方案。
  2. 在浏览器中捕获基准数据。
    • 打开用户指定的具体路由。
    • 在页面顶部、中部、底部/下方内容区域,以及布局可能不同的移动端视口分别进行性能分析。
    • 通过计算
      animationName
      animationPlayState
      和可见性统计CSS动画数量,包括
      ::before
      ::after
      伪元素。
    • 单独检查Canvas/WebGL元素;CSS性能分析无法证明RAF循环已停止。
    • 记录哪些动画名称在屏幕外仍在运行,以及对应的DOM元素所有者。
    • 针对内存泄漏排查需求,还需记录元素/Canvas/图片/iframe数量、可用的JS堆指标、10-30秒后的空闲采样数据,以及短路由循环采样数据。如果堆API返回
      null
      或浏览器沙箱阻止猴子补丁,需说明情况并依赖稳定的可观测计数及源代码审计。
    • 压力测试需控制范围。性能分析期间浏览器标签崩溃是过载的证据,但除非通过最小化测试复现,否则不要过度归因。
    • 可参考
      references/browser-profiling.md
      获取可复用的Codex Browser评估方法。
  3. 修改控制动画的最小范围代码。
    • 如果应用已有页面显示/可见性钩子,优先使用。
    • 否则添加
      IntersectionObserver
      ,为页面区块和动画子元素切换稳定的类名,例如
      is-offscreen
    • 通过针对性规则暂停CSS动画:
css
main > section.is-offscreen .expensive-animation,
.expensive-animation.is-offscreen {
  animation-play-state: paused !important;
}
  • 对于重复的卡片或占位符,观测卡片外壳及其动画子元素,而非整个文档。
  • 对于滚动字幕/滚动轨道,当其所在区块处于屏幕外时暂停轨道动画。
  • 对于骨架加载器和伪元素闪烁效果,必要时添加
    ::before
    ::after
    的暂停选择器。
  • 对于Canvas/WebGL/物理循环,直接控制RAF循环:
    • 当Canvas/容器进入可视区域时启动循环。
    • 当处于屏幕外时取消
      requestAnimationFrame
    • 重新进入可视区域时恢复循环。
    • 在清理阶段断开观察者并取消帧请求。
    • 若有助于浏览器验证,添加非可视化调试标记,例如
      data-animation-active
  • 如果组件已支持
    prefers-reduced-motion
    ,需保持兼容,避免因滚动/动画状态引发React渲染循环。
  • 针对内存泄漏加固:
    • 清除所有由特效创建的计时器/定时器。
    • 在组件卸载和重启循环前取消RAF。
    • 断开
      IntersectionObserver
      ResizeObserver
      MutationObserver
      和自定义订阅。
    • 使用相同的处理函数引用移除全局/窗口/文档监听器。
    • 销毁Three/WebGL的纹理、材质、几何体、渲染器,并移除渲染器DOM节点。
    • 终止针对DOM节点和可变对象(如着色器 uniforms)的GSAP补间/时间线。
    • 停止媒体流并暂停分离的视频/音频源。
    • 为异步加载器添加
      isDisposed
      标志,若在组件卸载后才完成加载,则释放已加载资源。
    • 在React清理逻辑中,如果代码检查工具警告ref可能发生变化,需在返回清理函数前捕获effect内的
      ref.current
      值。
    • 在可见性暂停后限制物理或模拟帧的增量,避免延迟帧执行过大的更新操作。
  1. 验证行为,而非仅验证构建结果。
    • 重新加载路由并重复执行顶部/中部/底部/移动端的性能分析。
    • 目标结果:测试页面区块的
      offscreenRunningCount: 0
    • 确认可视区域的动画仍能正常运行,或在滚动进入可视区域时恢复运行。
    • 确认RAF/Canvas循环在屏幕外时处于非活跃状态,在可视区域时处于活跃状态,或通过源代码/运行时状态证明已取消。
    • 针对内存泄漏审计,比较路由循环前后和空闲采样的数据。DOM/Canvas/图片数量在重复导航后应回到相同基准值,允许存在少量预期的异步内容变化。
    • 执行常规页面交互,如搜索/筛选/导航,确保观察者不会破坏动态内容。
    • 检查新标签页的控制台警告/错误。
  2. 运行本地检查。
    • 使用仓库的常规检查流程。对于React/Vite应用,通常是:
bash
git diff --check
npm run lint
npm run build
  • 将已知的非致命警告与错误分开说明。
  1. 若仓库/用户要求,进行窄范围提交。
    • 如果存在无关的未提交修改,使用独立索引:
bash
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.

7. 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.
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

   - 除非所有代码块都属于当前任务,否则不要从脏工作区暂存大范围文件。

7. 附带证据提交报告。
   - 先说明发现的问题:哪些内容仍在运行、哪些存在泄漏风险、哪些无法测量。
   - 将源代码审计风险与浏览器实时测量结果分开。
   - 包含具体的采样路由、屏幕外动画数量、DOM/Canvas计数稳定性、路由循环结果和本地检查情况。
   - 明确说明局限性,尤其是不可用的堆计数器或被阻止的浏览器工具。

Good Fix Patterns

优秀修复模式

  • Section-level
    is-offscreen
    plus element-level
    is-offscreen
    for long sections where below-the-fold child animations can still run.
  • Shared visibility selector constants per route, such as
    COURSES_PAGE_ANIMATION_VISIBILITY_SELECTOR
    .
  • IntersectionObserver
    thresholds around
    0.01
    for animation gating.
  • Direct RAF loop control for WebGL/canvas effects; CSS
    animation-play-state
    cannot 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.
  • isDisposed
    guards 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.
  • 区块级
    is-offscreen
    结合元素级
    is-offscreen
    ,适用于长页面中折叠区域下的子动画仍可能运行的场景。
  • 每个路由使用共享的可见性选择器常量,例如
    COURSES_PAGE_ANIMATION_VISIBILITY_SELECTOR
  • 用于动画控制的
    IntersectionObserver
    阈值设置为
    0.01
    左右。
  • 直接控制WebGL/Canvas特效的RAF循环;CSS的
    animation-play-state
    无法暂停JavaScript渲染循环。
  • 物理循环在暂停或延迟后恢复时限制帧增量。
  • 为GSAP/WebGL特效使用的React refs捕获清理节点。
  • 为可能在组件卸载后才完成的图片/视频/纹理/数据加载器添加
    isDisposed
    守卫。
  • 短时间的空闲和路由循环探测,以捕获累积的DOM节点、Canvas、iframe或未释放的媒体资源。

Avoid

避免事项

  • Removing all animations to make the profile pass.
  • Pausing visible hero motion because an ancestor selector is too broad.
  • Assuming
    animation-play-state
    covers 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.
  • 为了通过性能分析而移除所有动画。
  • 由于祖先选择器范围过宽而暂停可视区域的核心动画。
  • 假设
    animation-play-state
    能覆盖伪元素或JavaScript RAF循环。
  • 在长页面上仅依赖顶部的单次测量数据。
  • 将不可用的堆计数器视为无内存泄漏的证据。
  • 在浏览器中运行无限制的压力循环;使用有限循环并记录崩溃情况,但不要过度夸大因果关系。
  • 仅使用截图作为性能证明。
  • 在提交时附带无关的本地代码块。