前言
在前面的小节中,我们创建了一个场景,也进行了渲染。不过虽然我们移动旋转并缩放了立方体,但所有的画面都仍旧是静态的,如果以动画呈现该多好。
动画其实也很好实现,只需要有一个定时器,比如每间隔0.1秒就将立方体旋转1度,并进行渲染,就会形成连贯动画。
不过显示器有自己的刷新频率,比如60fps,意思是屏幕上每秒中会刷新显示60次图像。那么每一张图停留的时间大约是16毫秒。所以我们不需要自己创建定时器定时渲染,就让渲染和屏幕的刷新率一致即可。
在JS中我们可以执行一个函数window.requestAnimationFrame(...)
,这个函数可以获知屏幕刷新的时机。
准备工作
打开我们03节的项目代码,在场景中有一个红色的立方体。
requestAnimationFrame
requestAnimationFrame
这个方法接受一个函数作为参数,这个函数会在下一次屏幕刷新时调用。所以,如果在这个函数里继续调用requestAnimationFrame方法并传递相同的函数,那么这个函数将在每一次屏幕刷新时被调用,这样就等同于建立了一个完全和屏幕刷新时机一致的计时器。
现在我们创建一个名为tick的函数,在这个函数中,使用window.requestAnimationFrame(...)
并传递tick函数作为下次屏幕刷新时执行的函数:
/**
* Animate
*/
const tick = () =>
{
console.log('tick')
window.requestAnimationFrame(tick)
}
tick()
别忘了要调用一次tick函数哦。现在,我们拥有了一个不断循环执行的函数.
现在我们在这个tick函数内让立方体旋转,并在旋转后进行渲染renderer.render(...)
:
/**
* Animate
*/
const tick = () =>
{
mesh.rotation.y += 0.01
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick()
✌🏻,我们的立方体现在动起来了。
但目前这个方法其实会有一点小问题,这个立方体并非在所有的电脑上都保持相同的旋转速度。而是在电脑刷新率更快的电脑上旋转也更快,反之在刷新率更慢的电脑上,旋转也更慢。
THREE.Clock
如果我们能获知到屏幕刷新和电脑时间的关系,那么根据时间来设置旋转的角度,就可以所有屏幕上保持一样的旋转的速度。这有很多种方式可以做到,比如使用JS的Date
对象的实例方法new Date().getTime()
。
const time = new Date().getTime();
const tick = () =>{
const elapsedTime = (new Date().getTime()-time)/1000;
mesh.rotation.y = elapsedTime;
// ...
}
tick()
需要注意这个方法返回的时间单位为毫秒
,换算成单位秒
需要除以1000。
在Three.js中还有一个内置的名为Clock的对象,它专门用来处理时间计算。
我们只需实例化一个clock变量,并使用内置方法,如getElapsedTime ()
,就可以返回自创建时钟以来已经过去了多少秒
。
下面这个代码和上面使用new Date().getTime()
的效果完全一致
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
mesh.rotation.y = elapsedTime
// ...
}
tick()
你还记得初中数学知识三角函数的用法吗?现在让我们稍稍对代码做一些改变
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
mesh.position.x = Math.cos(elapsedTime)
mesh.position.y = Math.sin(elapsedTime)
// ...
}
tick()
如果你已经忘记了三角函数的相关知识,可能会惊讶为什么会出现这样的效果?那么去温习一下三角函数的相关知识吧,我们会经常用到,它能帮我们实现很多很酷的动画效果。
现在我们再换个思路,改变相机的坐标,并让相机持续lootAt看向立方体。
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
camera.position.x = Math.cos(elapsedTime)
camera.position.y = Math.sin(elapsedTime)
camera.lookAt(mesh.position)
// ...
}
tick()
动画库
有很多用于实现动画的库,其中一个非常著名的是GSAP,它是免费的。你可能用过TweenMax,TweenLite,它们和GSAP出自同一家公司。
我们依然使用<script>
标签引入它
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
GSAP的功能非常强大,本课程中并不会过多介绍GSAP的用法,它使用起来很简单,大家可以自行查阅文档或其他相关教程。现在我们把上面立方体旋转的代码修改一下,使用GSAP来完成立方体的旋转,并精确的控制旋转的时间。
下面的代码将使立方体在1秒内旋转45度
/**
* Animate
*/
gsap.to(mesh.rotation, { duration: 1, y: 45*(Math.PI/180) })
const tick = () =>{
renderer.render(scene, camera)
window.requestAnimationFrame(tick);
}
tick();
GSAP内也是使用requestAnimationFrame
来更新数值的,所以我们只需要持续在每一次屏幕刷新时渲染我们场景即可。
小结
大部分时候动画都不会只是简单的一直旋转,所以商业项目中我们更倾向于使用动画库,可以让我们更简单快捷的做出一些动画效果来。