TypeScript图形渲染实战:2D架构设计与实现》 —3 动画与Application类

网友投稿 816 2022-05-30

第2篇

Canvas2D篇

(  第3章  动画与Application类

(  第4章  使用Canvas2D绘图

第3章  动画与Application类

本章将从程序实现的角度来了解一下动画的原理,以及HTML 5提供的一些基础且必要的方法。

可以将动画的相关功能都封装到一个名为Application的类中,该类主要是作为应用程序的入口类。它能启动或关闭动画循环,抽象更新与重绘流程,提供事件分发和处理功能,并且具有一个允许以不同帧率运行的计时器。

在开始本章内容前先声明一下,本书所有的Demo以Chrome浏览器为主要测试环境,为简单起见,本书并不关注各浏览器之间的兼容性。

3.1  requestAnimationFrame方法与动画

从程序的角度来描述,笔者认为,动画就是不间断地、基于时间的更新与重绘。可以说这句话贯穿了本节要讲的所有内容。

3.1.1  HTML中不间断的循环

所谓不间断的,是指动画需要一个不停地重复循环机制,从程序实现的角度来说,一般有两种选择:

一种是类似于while ( true ) { }之类的死循环,除非满足退出死循环的条件,否则就一直不停地重复相同的行为。在Windows下的D3D / OpenGL开发中,经常使用这种模式来驱动动画不断运行。作为知识的延伸点,下面来看一段经典的Windows下基于C / C++语言的动画循环演示代码。若不感兴趣可直接跳过。具体代码如下:

MSG msg ;

ZeroMemory ( & msg , sizeof ( msg ) ) ;

// 只有明确地收到WM_QUIT消息,才跳出while循环,退出应用程序

// 否则一直循环重复相同的行为

《TypeScript图形渲染实战:2D架构设计与实现》 —3 动画与Application类

// Windows下经典的runLoop操作

while ( msg . message != WM_QUIT )

{

// 如果当前线程消息队列中有消息,则取出该消息

if ( PeekMessage ( & msg , NULL , 0U , 0U , PM_REMOVE ) ) {

//将键盘的虚拟键消息转换为WM_CHAR消息,并将WM_CHAR消息再次放入当前线程

消息队列中,下次还是可以被PeekMessage读取并处理

TranslateMessage ( & msg ) ;

//将当前的WM_开头的消息分发到Window 窗口过程处理回调函数中进行处理

DispatchMessage ( & msg ) ;

//上面的代码实际就是处理鼠标、键盘、WM_PAINT,或者计时器等队列消息

} else {

//如果当前线程消息队列中(上面的代码处理消息队列)没有消息可处理,就一直更新并重绘

Update ( ) ;                                                //更新

Render ( ) ;                                                //重绘

}

}

另外一种是类似于定时器的回调,例如使用HTML DOM(Document Object Model,文档对象模型)中Window对象的setTimeout、setInterval及requestAnimationFrame方法。关于setTimeout和setInterval的用法,请各位读者自行查阅相关资料(在3.4节中将实现类似setTimeout和setInterval的功能)。下面主要来看一下requestAnimationFrame方法的用法。具体代码如下:

// start记录的是第一次调用step函数的时间点,用于计算与第一次调用step函数的时间差,

以毫秒为单位

let start : number = 0 ;

//lastTime记录的是上一次调用step函数的时间点,用于计算两帧之间的时间差,以毫秒为单位

let lastTime : number = 0 ;

// count用于记录step函数运行的次数

let count : number = 0 ;

// step函数用于计算:

// 1.获取当前时间点与HTML程序启动时的时间差 : timestamp

// 2.获取当前时间点与第一次调用step时的时间差 : elapsedMsec

// 3.获取当前时间点与上一次调用step时的时间差 : intervalMsec

// step函数是作为requestAnimationFrame方法的回调函数使用的

// 因此step函数的签名必须是 ( timestamp : number ) => void

function step ( timestamp : number ) : void  {

// 第一次调用本函数时,设置start和lastTime为timestamp

if ( ! start ) start = timestamp ;

if ( ! lastTime ) lastTime = timestamp ;

// 计算当前时间点与第一次调用step时间点的差

let elapsedMsec : number = timestamp - start ;

// 计算当前时间点与上一次调用step时间点的差(可以理解为两帧之间的时间差)

let intervalMsec : number = timestamp - lastTime ;

// 记录上一次的时间戳

lastTime = timestamp ;

// 计数器,用于记录step函数被调用的次数

count ++ ;

console . log ( " " + count + " timestamp = " + timestamp ) ;

console . log ( " " + count + " elapsedMsec = " + elapsedMsec ) ;

console . log ( " " + count + " intervalMsec = " + intervalMsec) ;

// 使用requestAnimationFrame调用step函数

window . requestAnimationFrame ( step ) ;

}

// 使用requestAnimationFrame启动step

// 而step函数中又会调用requestAnimationFrame来回调step函数

// 从而形成不间断地递归调用,驱动动画不停地运行

window . requestAnimationFrame ( step ) ;

上述代码每次调用step函数会在浏览器的console控制台窗口中输出当前函数的调用次数,以及3个时间差的数值。Chrome浏览器中console控制台窗口输出的结果,如图3.1所示。

图3.1  Chrome浏览器中的requestAnimationFrame输出时间差

渲染 架构设计 TypeScript

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【IoT】零基础玩转华为IOT活动经历,个人体会
下一篇:我的Vuejs进阶之旅(六):指令
相关文章