本系列为 Three.js journey 教程学习笔记。
Animation 动画
Three.js 中的动画与其他 Canvas 动画类似,都是使用了 requestAnimationFrame
api,接下来就详细讲讲基于时间间隔动画、Threejs 内置的时钟、以及第三方动画库
基于时间间隔
为了避免高刷屏上动画速度变快,我们需要把动画播放与时间关联,而非帧数关联。
可以使用 requestAnimationFrame 获取时间差,动画基于这个时间差
import * as THREE from 'three'
import stats from '../common/stats'
// Canvas
const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement
// Scene
const scene = new THREE.Scene()
// Object
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
}),
)
scene.add(cube)
// Camera
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight)
camera.position.set(1, 1, 3)
camera.lookAt(cube.position)
// Renderer
const renderer = new THREE.WebGLRenderer({
canvas,
})
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
// Time
let time = Date.now()
// Animations
const tick = (currentTime: number) => {
stats.begin()
const deltaTime = currentTime - time
time = currentTime
cube.rotation.y += 0.001 * deltaTime
// Render
renderer.render(scene, camera)
stats.end()
requestAnimationFrame(tick)
}
tick(0)
效果如下:可以看到左上角的帧数为 144
基于 THREE 内置的 Clock
clock.getDelta()
实现动画
import * as THREE from 'three'
import stats from '../common/stats'
// Canvas
const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement
// Scene
const scene = new THREE.Scene()
// Object
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
}),
)
scene.add(cube)
// Camera
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight)
camera.position.set(1, 1, 3)
camera.lookAt(cube.position)
// Renderer
const renderer = new THREE.WebGLRenderer({
canvas,
})
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
// Clock
const clock = new THREE.Clock()
// Animations
const tick = () => {
stats.begin()
const delta = clock.getDelta()
cube.rotation.y += 1 * delta
// Render
renderer.render(scene, camera)
stats.end()
requestAnimationFrame(tick)
}
tick()
效果同上一个 demo
该对象用于跟踪时间。如果 performance.now 可用,则 Clock 对象通过该方法实现,否则回落到使用略欠精准的 Date.now 来实现。所以基于 three.js 内置的 Clock 理论上应该会比上一个示例更加精准
-
.getElapsedTime () : Float
获取自时钟启动后的秒数,同时将 .oldTime 设置为当前时间。 如果 .autoStart 设置为 true 且时钟并未运行,则该方法同时启动时钟。 -
.getDelta () : Float
获取自 .oldTime 设置后到当前的秒数。 同时将 .oldTime 设置为当前时间。 如果 .autoStart 设置为 true 且时钟并未运行,则该方法同时启动时钟。
getElapsedTime()
实现圆周运动
使用三角函数,可以让物体在三维空间做圆周运动
效果如下
import * as THREE from 'three'
import stats from '../common/stats'
// Canvas
const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement
// Scene
const scene = new THREE.Scene()
// Object
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0x607D8B,
}),
)
scene.add(cube)
// Camera
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight)
camera.position.set(0, 0, 3)
camera.lookAt(cube.position)
// Renderer
const renderer = new THREE.WebGLRenderer({
canvas,
})
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
// Clock
const clock = new THREE.Clock()
// Animations
const tick = () => {
stats.begin()
const elapsedTime = clock.getElapsedTime()
cube.position.y = Math.sin(elapsedTime)
cube.position.x = Math.cos(elapsedTime)
// Render
renderer.render(scene, camera)
stats.end()
requestAnimationFrame(tick)
}
tick()
其中核心代码是这 2 行
cube.position.y = Math.sin(elapsedTime)
cube.position.x = Math.cos(elapsedTime)
当然我们也可以让物体不动,让相机运动出现的效果相同
camera.position.y = Math.sin(elapsedTime)
camera.position.x = Math.cos(elapsedTime)
让相机总是指向方块
还可以让相机在圆周运动的同时总是指向物体,效果如下
// Animations
const tick = () => {
stats.begin()
const elapsedTime = clock.getElapsedTime()
camera.position.y = Math.sin(elapsedTime)
camera.position.x = Math.cos(elapsedTime)
camera.lookAt(cube.position)
// Render
renderer.render(scene, camera)
stats.end()
requestAnimationFrame(tick)
}
使用 GSAP 实现动画
除了手写位移或三角函数实现动画外,也可以使用第三方库来实现,例如下个例子使用了 GSAP
import * as THREE from 'three'
import gsap from 'gsap'
import stats from '../common/stats'
// Canvas
const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement
// Scene
const scene = new THREE.Scene()
// Object
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0x607d8b,
})
)
scene.add(cube)
// Camera
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight)
camera.position.set(0, 0, 3)
camera.lookAt(cube.position)
// Renderer
const renderer = new THREE.WebGLRenderer({
canvas,
})
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
gsap.fromTo(
cube.position,
{
x: -1.5,
},
{
x: 1.5,
duration: 1,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
},
)
// Animations
const tick = () => {
stats.begin()
// Render
renderer.render(scene, camera)
stats.end()
requestAnimationFrame(tick)
}
tick()
在线 demo 链接
小结
我们已经学习了解了几个实现 Three.js 动画的方法,什么场景选择什么方案并没有标准答案,这取决于你的项目你熟悉的库等等因素。大部分情况使用三角函数能够解决大部分问题,但对于复杂的动画,就需要依赖动画库了。
下一节将讲讲 Camera 相机。