threejs-animation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Three.js Motion (3D / WebGL)

Three.js 动效(3D / WebGL)

Build real-time 3D motion for the web: animated scenes, camera moves, GLTF playback, instancing, scroll-linked 3D, and React Three Fiber. Optimize for smooth 60fps and clean disposal.
为网页构建实时3D动效:动画场景、相机移动、GLTF播放、实例化、滚动关联3D以及React Three Fiber。优化以实现流畅的60fps帧率和干净的资源释放。

When to use

适用场景

  • 3D hero sections, product viewers, interactive/scroll-linked 3D backgrounds.
  • Playing and blending GLTF animation clips.
  • Rendering many objects efficiently (particle fields, tiles, forests).
  • Camera fly-throughs and scroll-driven camera moves.
  • 3D首屏区域、产品查看器、交互式/滚动关联3D背景。
  • 播放与混合GLTF动画片段。
  • 高效渲染大量对象(粒子场、瓦片、森林)。
  • 相机飞行动画和滚动驱动的相机移动。

Minimal scene + render loop

最小场景 + 渲染循环

js
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));   // cap DPR — critical for retina perf
document.body.appendChild(renderer.domElement);

const mesh = new THREE.Mesh(
  new THREE.IcosahedronGeometry(1, 0),
  new THREE.MeshStandardMaterial({ color: 0x44aaff, flatShading: true })
);
scene.add(mesh, new THREE.DirectionalLight(0xffffff, 2).translateZ(5),
          new THREE.AmbientLight(0xffffff, 0.4));

const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
  const dt = clock.getDelta();
  mesh.rotation.y += dt * 0.5;                            // frame-rate independent
  renderer.render(scene, camera);
});
addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});
Use
clock.getDelta()
to make motion frame-rate independent (multiply rates by
dt
), and
setAnimationLoop
(works with WebXR and pauses on tab blur) instead of raw
requestAnimationFrame
.
js
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));   // 限制设备像素比 —— 对视网膜屏性能至关重要
document.body.appendChild(renderer.domElement);

const mesh = new THREE.Mesh(
  new THREE.IcosahedronGeometry(1, 0),
  new THREE.MeshStandardMaterial({ color: 0x44aaff, flatShading: true })
);
scene.add(mesh, new THREE.DirectionalLight(0xffffff, 2).translateZ(5),
          new THREE.AmbientLight(0xffffff, 0.4));

const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
  const dt = clock.getDelta();
  mesh.rotation.y += dt * 0.5;                            // 帧率独立的动效
  renderer.render(scene, camera);
});
addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});
使用
clock.getDelta()
使动效独立于帧率(将速率乘以
dt
),并使用
setAnimationLoop
(支持WebXR,在标签页失焦时暂停)替代原生
requestAnimationFrame

GLTF animation clips

GLTF动画片段

Load a model, play clips through an
AnimationMixer
, and update the mixer with delta time every frame.
js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
let mixer, actions = {};
new GLTFLoader().load('model.glb', (gltf) => {
  scene.add(gltf.scene);
  mixer = new THREE.AnimationMixer(gltf.scene);
  gltf.animations.forEach((clip) => { actions[clip.name] = mixer.clipAction(clip); });
  actions['Idle']?.play();
});
// In the loop: if (mixer) mixer.update(dt);
Crossfade between two clips smoothly:
js
function crossfade(fromName, toName, dur = 0.4) {
  const from = actions[fromName], to = actions[toName];
  to.reset().setEffectiveWeight(1).play();
  from.crossFadeTo(to, dur, false);                       // warping=false: linear weight ramp
}
For one-shot clips (e.g. a jump), set
action.setLoop(THREE.LoopOnce); action.clampWhenFinished = true;
and listen for
mixer.addEventListener('finished', ...)
.
加载模型,通过
AnimationMixer
播放片段,并每帧用增量时间更新mixer
js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
let mixer, actions = {};
new GLTFLoader().load('model.glb', (gltf) => {
  scene.add(gltf.scene);
  mixer = new THREE.AnimationMixer(gltf.scene);
  gltf.animations.forEach((clip) => { actions[clip.name] = mixer.clipAction(clip); });
  actions['Idle']?.play();
});
// 在循环中:if (mixer) mixer.update(dt);
在两个片段间平滑交叉淡入淡出:
js
function crossfade(fromName, toName, dur = 0.4) {
  const from = actions[fromName], to = actions[toName];
  to.reset().setEffectiveWeight(1).play();
  from.crossFadeTo(to, dur, false);                       // warping=false:线性权重过渡
}
对于一次性片段(如跳跃),设置
action.setLoop(THREE.LoopOnce); action.clampWhenFinished = true;
并监听
mixer.addEventListener('finished', ...)

Camera lerp and scroll-linked motion

相机插值与滚动关联动效

Smooth camera follow uses exponential interpolation toward a target. Frame-rate-correct damping:
js
const target = new THREE.Vector3(0, 0, 5);
function dampVec(current, goal, lambda, dt) {
  current.lerp(goal, 1 - Math.exp(-lambda * dt));         // lambda ~ 3..8
}
// loop: dampVec(camera.position, target, 5, dt); camera.lookAt(0, 0, 0);
Scroll-linked camera: map
scrollY / maxScroll
(0→1) to camera position along a path. Update the goal on scroll, let damping smooth it.
js
addEventListener('scroll', () => {
  const p = scrollY / (document.body.scrollHeight - innerHeight);
  target.set(0, p * -10, 5 - p * 3);                      // move down and in
});
Use
THREE.CatmullRomCurve3
and
curve.getPointAt(p)
for curved fly-throughs.
平滑相机跟随使用指数插值趋近目标。帧率修正的阻尼效果:
js
const target = new THREE.Vector3(0, 0, 5);
function dampVec(current, goal, lambda, dt) {
  current.lerp(goal, 1 - Math.exp(-lambda * dt));         // lambda 取值约3..8
}
// 循环中:dampVec(camera.position, target, 5, dt); camera.lookAt(0, 0, 0);
滚动关联相机:将
scrollY / maxScroll
(0→1)映射到相机沿路径的位置。在滚动时更新目标,让阻尼效果平滑过渡。
js
addEventListener('scroll', () => {
  const p = scrollY / (document.body.scrollHeight - innerHeight);
  target.set(0, p * -10, 5 - p * 3);                      // 向下并向内移动
});
使用
THREE.CatmullRomCurve3
curve.getPointAt(p)
实现曲线飞行动画。

Instancing for many objects

大量对象的实例化

One draw call for thousands of identical meshes. Set per-instance matrices (and optional colors).
js
const count = 5000;
const geo = new THREE.BoxGeometry(0.1, 0.1, 0.1);
const mat = new THREE.MeshStandardMaterial();
const inst = new THREE.InstancedMesh(geo, mat, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
  dummy.position.set((Math.random()-0.5)*20, (Math.random()-0.5)*20, (Math.random()-0.5)*20);
  dummy.updateMatrix();
  inst.setMatrixAt(i, dummy.matrix);
}
inst.instanceMatrix.needsUpdate = true;
scene.add(inst);
// Animate: update dummy per instance, setMatrixAt, then needsUpdate = true each frame.
Set
inst.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
if updating every frame. Per-instance color:
inst.setColorAt(i, color)
then
inst.instanceColor.needsUpdate = true
.
一次绘制调用渲染数千个相同网格。设置每个实例的矩阵(可选颜色)。
js
const count = 5000;
const geo = new THREE.BoxGeometry(0.1, 0.1, 0.1);
const mat = new THREE.MeshStandardMaterial();
const inst = new THREE.InstancedMesh(geo, mat, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
  dummy.position.set((Math.random()-0.5)*20, (Math.random()-0.5)*20, (Math.random()-0.5)*20);
  dummy.updateMatrix();
  inst.setMatrixAt(i, dummy.matrix);
}
inst.instanceMatrix.needsUpdate = true;
scene.add(inst);
// 动画:更新每个实例的dummy,调用setMatrixAt,然后每帧设置needsUpdate = true。
如果每帧更新,设置
inst.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
。每个实例的颜色:
inst.setColorAt(i, color)
然后
inst.instanceColor.needsUpdate = true

React Three Fiber (R3F)

React Three Fiber (R3F)

Drive animation with
useFrame
; it receives
state
and
delta
.
jsx
import { Canvas, useFrame } from '@react-three/fiber';
import { useRef } from 'react';
function Spinner() {
  const ref = useRef();
  useFrame((state, delta) => {
    ref.current.rotation.y += delta * 0.5;
    ref.current.position.y = Math.sin(state.clock.elapsedTime) * 0.3;
  });
  return <mesh ref={ref}><icosahedronGeometry /><meshStandardMaterial /></mesh>;
}
export default () => (
  <Canvas dpr={[1, 2]} camera={{ position: [0, 0, 5] }}>
    <ambientLight intensity={0.4} /><directionalLight position={[5, 5, 5]} />
    <Spinner />
  </Canvas>
);
dpr={[1, 2]}
caps pixel ratio. Load models with
useGLTF
(drei), animate clips with
useAnimations
. For springs use
@react-spring/three
; for scroll use drei
ScrollControls
+
useScroll
.
使用
useFrame
驱动动画;它接收
state
delta
参数。
jsx
import { Canvas, useFrame } from '@react-three/fiber';
import { useRef } from 'react';
function Spinner() {
  const ref = useRef();
  useFrame((state, delta) => {
    ref.current.rotation.y += delta * 0.5;
    ref.current.position.y = Math.sin(state.clock.elapsedTime) * 0.3;
  });
  return <mesh ref={ref}><icosahedronGeometry /><meshStandardMaterial /></mesh>;
}
export default () => (
  <Canvas dpr={[1, 2]} camera={{ position: [0, 0, 5] }}>
    <ambientLight intensity={0.4} /><directionalLight position={[5, 5, 5]} />
    <Spinner />
  </Canvas>
);
dpr={[1, 2]}
限制像素比。使用
useGLTF
(drei库)加载模型,使用
useAnimations
动画片段。弹簧动效使用
@react-spring/three
;滚动关联使用drei的
ScrollControls
+
useScroll

Performance and cleanup (critical)

性能与清理(至关重要)

  • Cap pixel ratio:
    Math.min(devicePixelRatio, 2)
    . This is the single biggest win on retina/mobile.
  • Reuse geometries and materials; merge static geometry with
    BufferGeometryUtils.mergeGeometries
    .
  • Prefer matcaps or baked lighting over many real-time lights; each shadow-casting light is expensive.
  • Material/emissive motion (pulsing emissive, displacement) often reads better and is cheaper than moving lots of geometry.
  • 限制设备像素比:
    Math.min(devicePixelRatio, 2)
    。这是提升视网膜屏/移动端性能最有效的手段。
  • 复用几何体和材质;使用
    BufferGeometryUtils.mergeGeometries
    合并静态几何体。
  • 优先使用材质捕获(matcaps)或烘焙光照,而非大量实时光源;每个投射阴影的光源都很耗费性能。
  • 材质/自发光动效(脉冲自发光、位移)通常视觉效果更好,且比移动大量几何体更节省性能。

Cleanup & GPU memory (disposal)

清理与GPU内存(资源释放)

GPU resources (geometries, materials, textures, render targets) live on the GPU, not the JS heap. The garbage collector frees the JS objects but cannot free the WebGL buffers/textures they uploaded. Without explicit
.dispose()
, VRAM grows on every mount until the context is lost (black canvas,
WebGL: CONTEXT_LOST_WEBGL
). This is why
renderer.info.memory
keeps rising across mount/unmount cycles.
Dispose what allocates GPU memory; ignore what does not:
  • Dispose:
    geometry.dispose()
    (VBOs),
    material.dispose()
    (shader program — but NOT its textures),
    texture.dispose()
    (per map slot:
    map
    ,
    normalMap
    ,
    roughnessMap
    ,
    envMap
    , …),
    renderTarget.dispose()
    , and on final teardown
    renderer.dispose()
    +
    renderer.forceContextLoss()
    .
  • No disposal needed:
    Mesh
    /
    Group
    /
    Scene
    (GC reclaims after removal),
    Vector3
    /
    Matrix4
    /
    Color
    (plain JS), lights/cameras (except shadow map render targets).
  • scene.remove(mesh)
    only detaches from the graph — it frees nothing on the GPU. Geometry/material/textures stay resident until explicitly disposed.
Teardown traversal — disposing a loaded model or whole scene must handle material arrays and every texture map per material:
js
function disposeObject(root) {
  root.traverse((obj) => {
    if (obj.geometry) obj.geometry.dispose();
    if (obj.material) {
      const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
      for (const m of mats) {
        for (const key in m) {                  // dispose any texture property
          const v = m[key];
          if (v && v.isTexture) v.dispose();
        }
        m.dispose();
      }
    }
  });
  root.parent?.remove(root);
}
// SPA route teardown: renderer.setAnimationLoop(null); disposeObject(scene);
//   renderer.dispose(); renderer.forceContextLoss?.();
R3F unmount cleanup — resources created declaratively in JSX are auto-disposed on unmount (R3F walks attached objects and calls
.dispose()
). Leaks happen with imperative resources (
new THREE.X()
in hooks/refs/loaders) that R3F never attached. Create them with
useMemo
and dispose in a matching
useEffect
cleanup:
jsx
const geometry = useMemo(() => new THREE.BufferGeometry(/* … */), [count]);
useEffect(() => () => geometry.dispose(), [geometry]);  // disposes on unmount AND dep change
For shared resources reused across mounts, set
dispose={null}
on the JSX object and dispose only at app shutdown. Verify with
renderer.info.memory
(R3F:
useThree().gl.info.memory
): mount/unmount 10x; if
geometries
/
textures
does not return to baseline, something leaked.
See
references/resource-disposal.md
for the full disposal utility (all map slots, shader uniforms, render targets, skinned meshes), EffectComposer/render-target disposal, a
renderer.info
leak-test harness, R3F cleanup recipes (refs, textures, GLTF cache clearing,
<DisposeOnUnmount>
), and HMR safety.
GPU资源(几何体、材质、纹理、渲染目标)存储在GPU中,而非JS堆内存。垃圾回收器会释放JS对象,但无法释放它们上传的WebGL缓冲区/纹理。如果不显式调用
.dispose()
,VRAM会在每次挂载时增长,直到上下文丢失(画布变黑,出现
WebGL: CONTEXT_LOST_WEBGL
错误)。这就是为什么
renderer.info.memory
在挂载/卸载循环中持续上升的原因。
释放占用GPU内存的资源;忽略不占用的资源:
  • 需要释放:
    geometry.dispose()
    (顶点缓冲区对象VBO)、
    material.dispose()
    (着色器程序——但不包括其纹理)、
    texture.dispose()
    (每个贴图插槽:
    map
    normalMap
    roughnessMap
    envMap
    等)、
    renderTarget.dispose()
    ,以及在最终销毁时调用
    renderer.dispose()
    +
    renderer.forceContextLoss()
  • 无需释放:
    Mesh
    /
    Group
    /
    Scene
    (移除后由垃圾回收器回收)、
    Vector3
    /
    Matrix4
    /
    Color
    (纯JS对象)、光源/相机(阴影贴图渲染目标除外)。
  • scene.remove(mesh)
    仅将对象从场景图中分离——不会释放任何GPU资源。几何体/材质/纹理会一直驻留,直到显式释放。
遍历销毁——释放加载的模型或整个场景时,必须处理材质数组以及每个材质的所有纹理贴图
js
function disposeObject(root) {
  root.traverse((obj) => {
    if (obj.geometry) obj.geometry.dispose();
    if (obj.material) {
      const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
      for (const m of mats) {
        for (const key in m) {                  // 释放任何纹理属性
          const v = m[key];
          if (v && v.isTexture) v.dispose();
        }
        m.dispose();
      }
    }
  });
  root.parent?.remove(root);
}
// SPA路由销毁:renderer.setAnimationLoop(null); disposeObject(scene);
//   renderer.dispose(); renderer.forceContextLoss?.();
R3F卸载清理——在JSX中声明式创建的资源会在卸载时自动释放(R3F会遍历关联对象并调用
.dispose()
)。内存泄漏发生在命令式创建的资源(在钩子/引用/加载器中使用
new THREE.X()
),这些资源从未被R3F关联。使用
useMemo
创建它们,并在对应的
useEffect
清理函数中释放:
jsx
const geometry = useMemo(() => new THREE.BufferGeometry(/* … */), [count]);
useEffect(() => () => geometry.dispose(), [geometry]);  // 在卸载和依赖项变化时释放
对于跨挂载复用的共享资源,在JSX对象上设置
dispose={null}
,仅在应用关闭时释放。使用
renderer.info.memory
(R3F中为
useThree().gl.info.memory
)验证:挂载/卸载10次;如果
geometries
/
textures
未回到初始值,则存在内存泄漏。
查看
references/resource-disposal.md
获取完整的资源释放工具(所有贴图插槽、着色器 uniforms、渲染目标、蒙皮网格)、EffectComposer/渲染目标销毁、完整渲染器销毁、R3F清理方案(引用、纹理、GLTF缓存清理、
<DisposeOnUnmount>
)以及HMR安全策略。

Deliver & verify (standalone HTML)

交付与验证(独立HTML)

Packaged helper (
scripts/
):
scripts/seek-shot.sh anim.html 0 1.5 3
freezes the
?t=N
harness and screenshots each moment;
scripts/contact-sheet.sh sheet.png frame-*.png
tiles them for one-glance review. See
scripts/README.md
.
For a self-contained 3D scene (hero, WebGL background, micro-scene) the deliverable is one HTML file that opens directly in a browser — Three.js pulled from a CDN via an importmap, one render loop, no build step. A single file is the right tier for a scene; don't reach for a bundler when one file does the job.
Output contract:
  • One
    .html
    : importmap pins
    three
    +
    three/addons
    to a CDN; your scene and the render loop in one inline
    <script type="module">
    .
  • Drive ALL motion from one source of truth —
    clock.getElapsedTime()
    (and
    mixer
    /camera read from it). No
    Date.now()
    , no per-object wall-clock.
  • Seed any randomness (instance placement, jitter) from a fixed seed so frames reproduce.
Seek/freeze harness — render ONE frame at a fixed time for screenshots.
?t=N
forces elapsed time to
N
seconds, advances the scene to that instant, renders once, and stops the loop — a deterministic still.
html
<script type="module">
  // ... build scene, camera, renderer, clock, mixer ...
  const t = new URLSearchParams(location.search).get("t");
  function frame(elapsed) {            // pure: scene state is a function of elapsed
    mesh.rotation.y = elapsed * 0.5;
    if (mixer) { mixer.setTime(elapsed); }   // setTime is absolute, not delta
    renderer.render(scene, camera);
  }
  if (t !== null) {
    frame(parseFloat(t));             // one fixed frame, no loop
    window.__ready = true;
  } else {
    const clock = new THREE.Clock();
    renderer.setAnimationLoop(() => frame(clock.getElapsedTime()));
  }
</script>
Verify loop — render → freeze → screenshot → check: open the file at three instants — start, mid, end (
?t=0
,
?t=<mid>
,
?t=<end>
for an animated clip/loop) — screenshot each, and check both fidelity (matches the brief) and artifacts: a black canvas = parse/init error (check the console), clipped/off-frame geometry, NaN positions (objects vanish), missing model/texture (CDN 404). WebGL needs a GPU context; Playwright/Chromium supplies one (swiftshader) headless.
bash
npx playwright screenshot --wait-for-timeout=600 "file://$PWD/scene.html?t=1.5" frame-mid.png
Before you finish:
  1. Canvas actually renders — not blank, no console/WebGL errors, no CDN 404s.
  2. ?t=N
    freezes a reproducible frame (same N → same pixels; randomness seeded).
  3. Screenshotted at start / mid / end — matches the brief, no clipping/NaN/black.
  4. Disposed and leak-free if embedded in an SPA (
    setAnimationLoop(null)
    + dispose; see disposal section).
  5. prefers-reduced-motion
    honored — slow/halt rotation or auto-play where relevant.
打包工具
scripts/
目录):
scripts/seek-shot.sh anim.html 0 1.5 3
会冻结
?t=N
工具并截取每个时刻的截图;
scripts/contact-sheet.sh sheet.png frame-*.png
将截图拼接成一张预览图。详见
scripts/README.md
对于独立的3D场景(首屏、WebGL背景、微型场景),交付物应为可直接在浏览器中打开的单个HTML文件——Three.js通过importmap从CDN引入,单个渲染循环,无需构建步骤。单个文件是场景的最佳交付形式;当单个文件就能满足需求时,无需使用打包工具。
输出规范:
  • 单个
    .html
    文件:importmap将
    three
    +
    three/addons
    固定到CDN;场景和渲染循环写在单个内联
    <script type="module">
    中。
  • 所有动效由单一数据源驱动——
    clock.getElapsedTime()
    mixer
    /相机从中读取)。禁止使用
    Date.now()
    ,禁止每个对象使用独立的壁钟时间。
  • 任何随机性(实例位置、抖动)使用固定种子,确保帧可复现。
定位/冻结工具——在固定时间渲染单帧用于截图
?t=N
将流逝时间强制设置为
N
秒,将场景推进到该时刻,渲染一次后停止循环——生成确定性的静态画面。
html
<script type="module">
  // ... 构建场景、相机、渲染器、时钟、mixer ...
  const t = new URLSearchParams(location.search).get("t");
  function frame(elapsed) {            // 纯函数:场景状态是流逝时间的函数
    mesh.rotation.y = elapsed * 0.5;
    if (mixer) { mixer.setTime(elapsed); }   // setTime是绝对时间,而非增量时间
    renderer.render(scene, camera);
  }
  if (t !== null) {
    frame(parseFloat(t));             // 单帧固定画面,无循环
    window.__ready = true;
  } else {
    const clock = new THREE.Clock();
    renderer.setAnimationLoop(() => frame(clock.getElapsedTime()));
  }
</script>
验证循环——渲染→冻结→截图→检查: 在三个时刻打开文件——开始、中间、结束(动画片段/循环使用
?t=0
?t=<mid>
?t=<end>
)——分别截图,检查保真度(符合需求)和瑕疵黑色画布=解析/初始化错误(检查控制台)、几何体被裁剪/超出画面、NaN位置(对象消失)、模型/纹理缺失(CDN 404)。WebGL需要GPU上下文;Playwright/Chromium提供无头模式的swiftshader GPU上下文。
bash
npx playwright screenshot --wait-for-timeout=600 "file://$PWD/scene.html?t=1.5" frame-mid.png
完成前检查:
  1. 画布实际渲染——无空白、无控制台/WebGL错误、无CDN 404。
  2. ?t=N
    可冻结可复现的帧(相同N→相同像素;随机性已播种)。
  3. 在开始/中间/结束时刻截图——符合需求,无裁剪/NaN/黑色画面。
  4. 嵌入SPA时可释放且无内存泄漏(
    setAnimationLoop(null)
    + 释放;见资源释放章节)。
  5. 遵循
    prefers-reduced-motion
    ——在相关位置减慢/停止旋转或自动播放。

Quick reference

快速参考

GoalAPI
Frame-independent motionmultiply by
clock.getDelta()
Play GLTF clip
mixer.clipAction(clip).play()
+
mixer.update(dt)
Blend clips
from.crossFadeTo(to, dur, false)
Smooth camera
lerp(goal, 1 - exp(-lambda*dt))
Many objects
InstancedMesh
+
setMatrixAt
Scroll 3Dmap scroll 0→1 to camera target
Cap DPR
setPixelRatio(min(dpr, 2))
目标API
帧率独立动效乘以
clock.getDelta()
播放GLTF片段
mixer.clipAction(clip).play()
+
mixer.update(dt)
混合片段
from.crossFadeTo(to, dur, false)
平滑相机
lerp(goal, 1 - exp(-lambda*dt))
大量对象
InstancedMesh
+
setMatrixAt
滚动关联3D将滚动值0→1映射到相机目标
限制设备像素比
setPixelRatio(min(dpr, 2))

Reference files

参考文件

  • references/r3f-and-perf.md
    — Full AnimationMixer crossfade and one-shot handling, useFrame patterns (clock, pointer, lerp), drei ScrollControls scroll-linked camera, InstancedMesh per-frame animation with color, pixel-ratio and adaptive resolution, complete dispose-on-unmount routine, and postprocessing (UnrealBloom, DepthOfField/bokeh) for both vanilla Three and R3F.
  • references/resource-disposal.md
    — Complete GPU resource disposal: full disposal utility (all map slots, shader uniforms, render targets, env maps, skinned meshes), EffectComposer/render-target teardown, full renderer teardown, R3F leak cases and the useMemo+cleanup pattern, ref/texture/GLTF cleanup recipes,
    <DisposeOnUnmount>
    helper, shared-resource and InstancedMesh strategies,
    renderer.info
    leak-test harness, and HMR safety.
  • references/r3f-and-perf.md
    ——完整的AnimationMixer交叉淡入淡出和一次性片段处理、useFrame模式(时钟、指针、插值)、drei ScrollControls滚动关联相机、带颜色的InstancedMesh逐帧动画、像素比和自适应分辨率、完整的卸载释放流程,以及原生Three和R3F的后期处理(UnrealBloom、DepthOfField/散景)。
  • references/resource-disposal.md
    ——完整的GPU资源释放:全功能释放工具(所有贴图插槽、着色器uniforms、渲染目标、环境贴图、蒙皮网格)、EffectComposer/渲染目标销毁、完整渲染器销毁、R3F内存泄漏场景和useMemo+清理模式、引用/纹理/GLTF清理方案、
    <DisposeOnUnmount>
    工具、共享资源和InstancedMesh策略、
    renderer.info
    内存泄漏测试工具,以及HMR安全策略。