JS:指定FPS帧频,requestAnimationFrame播放动画

Flash制作动画,最基础的概念就是帧,但在Flash中,帧频的控制比较简单,只需要编译前指定一下目标帧频就可以了。

实际运行时,不需要我们关心定时器的问题,flash player会定时触发EnterFrame消息,推动Movieclip播放。

在js这一侧,需要我们设定一个定时器,并推动相应的绘制逻辑执行。

最简单:

var FPS = 60;

setInterval(draw, 1000/FPS);

这个简单做法,如果draw带有大量逻辑计算,导致计算时间超过帧等待时间时,将会出现丢帧。除外,如果FPS太高,超过了当时浏览器的重绘频率,将会造成计算浪费,例如浏览器实际才重绘2帧,但却计算了3帧,那么有1帧的计算就浪费了。

成熟做法:

引入requestAnimationFrame,这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数,以满足开发者操作动画的需求。

这个函数类似setTimeout,只调用一次。

function draw() { 
        requestAnimationFrame(draw); 
        // ... Code for Drawing the Frame ... 
}

递归调用,就可以实现定时器。

但是,这样完全跟浏览器帧频同步了,无法自定义动画的帧频,是无法满足需求的。

接下来需要考虑如何控制帧频。

简单做法:

var fps = 30;
function tick() {
  setTimeout(function() {
    requestAnimationFrame(tick);
    draw(); // ... Code for Drawing the Frame ...
  }, 1000 / fps);
}
tick();

这种做法,比较直观的可以发现,每一次setTimeout执行的时候,都还要再等到下一个requestAnimationFrame事件到达,累积下去会造成动画变慢。

自行控制时间跨度:

var fps = 30;
var now;
var then = Date.now();
var interval = 1000/fps;
var delta;

function tick() {
  requestAnimationFrame(tick);
  now = Date.now();
  delta = now - then;
  if (delta > interval) {
    // 这里不能简单then=now,否则还会出现上边简单做法的细微时间差问题。例如fps=10,每帧100ms,而现在每16ms(60fps)执行一次draw。16*7=112>100,需要7次才实际绘制一次。这个情况下,实际10帧需要112*10=1120ms>1000ms才绘制完成。
    then = now - (delta % interval);
    draw(); // ... Code for Drawing the Frame ...
  }
}
tick();

针对低版本浏览器再优化:

如果浏览器没有requestAnimationFrame函数,实际底层还只能用setTimeout模拟,上边做的都是无用功。那么可以再改进一下。

var fps = 30;
var now;
var then = Date.now();
var interval = 1000/fps;
var delta;
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

function tick() {
  if(window.requestAnimationFrame)
   {
      requestAnimationFrame(tick);
      now = Date.now();
      delta = now - then;
      if (delta > interval) {
        // 这里不能简单then=now,否则还会出现上边简单做法的细微时间差问题。例如fps=10,每帧100ms,而现在每16ms(60fps)执行一次draw。16*7=112>100,需要7次才实际绘制一次。这个情况下,实际10帧需要112*10=1120ms>1000ms才绘制完成。
        then = now - (delta % interval);
        draw(); // ... Code for Drawing the Frame ...
      }
   }
   else
   {
       setTimeout(tick, interval);
    draw();
   }
}
tick();
            

最后,还可以加上暂停。

var fps = 30;
var pause = false;
var now;
var then = Date.now();
var interval = 1000/fps;
var delta;
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

function tick() {
     if(pause)
          return;
   if(window.requestAnimationFrame)
     {
    ...
     }
     else
     {
          ...
     }
}
tick();

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ThoughtWorks

前端不止:Web性能优化 - 关键渲染路径以及优化策略

我问你:“当你从搜索引擎的结果页面选择打开一条搜索结果时,你觉得多长时间之后,如果页面还处于白屏或者没有加载到关键信息,你会选择关掉这个窗口?”

14320
来自专栏腾讯Bugly的专栏

《Pluto - iOS 上一个高性能的排版渲染引擎》

| 导语 Pluto 是 iOS 上的一个排版渲染引擎,通过 JSON/JS 文件可以很方便地描述界面元素,开发效率很高,并且在流畅度,内存等方便有保证 Qzo...

34670
来自专栏Hans362 's Lab

UTF8-BOM编码导致Html顶部白条问题

自从给博客加上了Google Adsense的广告,就出现了一个很困扰我的问题:网页顶部莫名空出了一个白条。虽然对于网页的访问没有什么影响,但是...强迫症不能...

26410
来自专栏angularejs学习篇

js鼠标事件

今天遇到一个非常奇怪而又搞笑的事情:给一个a标签添加一个鼠标移动上时给一个事件,我给其添加的是一个onMouseMove事件,结果在IE6 7 8 9和GOOL...

23230
来自专栏世界第一语言是java

vue2(webpack)调用amap高德地图及其UI组件vue2(webpack)调用amap高德地图及其UI组件和标记物#

30730
来自专栏進无尽的文章

基础篇-app上传小准备及上架后搜索不显示

        app上传中会需要准备一些文件,如 icon图标,launch Image ,itunes Contect 中还需要上传不同尺寸的屏幕截图等,下...

25420
来自专栏张戈的专栏

利用placeholder属性来添加输入框默认文字提示,提高用户体验

这个是我老早就在用的方法,本不打算分享,因为网上随便都能搜得到,感觉没有分享的必要。鉴于中国博客联盟有朋友问到了这问题,所以还是简单的整理下,希望能帮到部分不了...

44190
来自专栏DeveWork

WordPress 开发之使用WordPress 3.8+后台图标(dashicons)

伴随着WordPress 3.8 的新界面,WordPress 官方为后台UI 引进了目前贼流行的webfont(又称 icon font),官方独立项目名为d...

21180
来自专栏编程微刊

三个Bootstrap免费字体和图标库

31940
来自专栏腾讯NEXT学位

【干货】解剖小程序的 setData

双线程的渲染机制、通信机制,setData 的出现、工作原理、使用建议等,应该要怎么去理解呢?

88930

扫码关注云+社区

领取腾讯云代金券