用requestAnimationFrame控制fps?


140

现在看来似乎requestAnimationFrame是事实上的动画方法。在大多数情况下,它对我来说效果很好,但是现在我正在尝试制作一些画布动画,我在想:是否有任何方法可以确保它以特定的fps运行?我了解到rAF的目的是使动画始终保持平滑,并且可能冒使动画不稳定的风险,但是现在看来它以任意不同的速度运行,我想知道是否有一种方法可以打击那不知何故。

我会用,setInterval但是我想要rAF提供的优化(尤其是在标签页处于焦点时自动停止)。

如果有人想看一下我的代码,那就差不多了:

animateFlash: function() {
    ctx_fg.clearRect(0,0,canvasWidth,canvasHeight);
    ctx_fg.fillStyle = 'rgba(177,39,116,1)';
    ctx_fg.strokeStyle = 'none';
    ctx_fg.beginPath();
    for(var i in nodes) {
        nodes[i].drawFlash();
    }
    ctx_fg.fill();
    ctx_fg.closePath();
    var instance = this;
    var rafID = requestAnimationFrame(function(){
        instance.animateFlash();
    })

    var unfinishedNodes = nodes.filter(function(elem){
        return elem.timer < timerMax;
    });

    if(unfinishedNodes.length === 0) {
        console.log("done");
        cancelAnimationFrame(rafID);
        instance.animate();
    }
}

其中Node.drawFlash()只是一些代码,这些代码根据计数器变量确定半径,然后绘制一个圆。


1
您的动画滞后吗?我认为最大的好处requestAnimationFrame是(如名称所示)仅在需要时才请求动画帧。假设您显示一个静态的黑色画布,由于不需要新的帧,因此应该获得0 fps。但是,如果要显示要求60fps的动画,则也应该这样做。rAF只允许“跳过”无用的帧,然后节省CPU。
maxdec

setInterval在无效选项卡中也不起作用。
ViliusL 2013年

此代码在90hz显示屏与60hz显示屏与144hz显示屏上的运行方式不同。
manthrax

Answers:


190

如何将requestAnimationFrame限制为特定帧速率

演示以5 FPS的速度节流:http : //jsfiddle.net/m1erickson/CtsY3/

此方法通过测试自执行最后一个帧循环以来的经过时间来工作。

仅当指定的FPS间隔过去时,图形代码才会执行​​。

代码的第一部分设置了一些用于计算经过时间的变量。

var stop = false;
var frameCount = 0;
var $results = $("#results");
var fps, fpsInterval, startTime, now, then, elapsed;


// initialize the timer variables and start the animation

function startAnimating(fps) {
    fpsInterval = 1000 / fps;
    then = Date.now();
    startTime = then;
    animate();
}

这段代码是实际的requestAnimationFrame循环,以您指定的FPS绘制。

// the animation loop calculates time elapsed since the last loop
// and only draws if your specified fps interval is achieved

function animate() {

    // request another frame

    requestAnimationFrame(animate);

    // calc elapsed time since last loop

    now = Date.now();
    elapsed = now - then;

    // if enough time has elapsed, draw the next frame

    if (elapsed > fpsInterval) {

        // Get ready for next frame by setting then=now, but also adjust for your
        // specified fpsInterval not being a multiple of RAF's interval (16.7ms)
        then = now - (elapsed % fpsInterval);

        // Put your drawing code here

    }
}

5
出色的解释和示例。这应该标记为已接受的答案
muxcmux 2014年

13
不错的演示-应该接受。在这里,请拨弄您的小提琴,以演示使用window.performance.now()而不是Date.now()。这与rAF已经收到的高分辨率时间戳很好地匹配,因此无需在回调内调用Date.now():jsfiddle.net/chicagogrooves/nRpVD/2
Dean Radcliffe

2
感谢您使用新的rAF时间戳功能更新了链接。新的rAF时间戳添加了有用的基础设施,并且比Date.now更精确。
2014年

13
这是一个非常不错的演示,它启发了我自己制作(JSFiddle)。主要区别是使用rAF(如Dean的演示)代替Date;添加控件以动态调整目标帧速率;在与动画不同的间隔上采样帧速率;以及添加历史帧速率图。
tavnab

1
您只能控制何时跳帧。60 fps的监视器始终以16ms的间隔绘制。例如,如果您希望游戏以50fps的速度运行,则希望每6帧跳过一次。您检查是否经过了20毫秒(1000/50),并且还没有(仅经过16毫秒),所以跳过了一个帧,然后从绘制开始,下一帧又经过了32毫秒,因此进行绘制和重置。但是,然后您将跳过一半的帧并以30fps的速度运行。因此,当您重置时,请记住您上次等待时间太长了12ms。因此,下一帧又经过了16毫秒,但您将其视为16 + 12 = 28毫秒,因此您再次绘制,并且等待了8毫秒太长的时间
Curtis

47

更新2016/6

限制帧速率的问题在于屏幕具有恒定的更新速率,通常为60 FPS。

如果我们想要24 FPS,我们将永远无法在屏幕上获得真正的24 fps,我们可以像这样计时,但无法显示,因为显示器只能以15 fps,30 fps或60 fps的速率显示同步帧(某些显示器也可以120 fps的速率) )。

但是,出于计时目的,我们可以在可能的情况下进行计算和更新。

您可以通过将计算和回调封装到对象中来构建用于控制帧速率的所有逻辑:

function FpsCtrl(fps, callback) {

    var delay = 1000 / fps,                               // calc. time per frame
        time = null,                                      // start time
        frame = -1,                                       // frame count
        tref;                                             // rAF time reference

    function loop(timestamp) {
        if (time === null) time = timestamp;              // init start time
        var seg = Math.floor((timestamp - time) / delay); // calc frame no.
        if (seg > frame) {                                // moved to next frame?
            frame = seg;                                  // update
            callback({                                    // callback function
                time: timestamp,
                frame: frame
            })
        }
        tref = requestAnimationFrame(loop)
    }
}

然后添加一些控制器和配置代码:

// play status
this.isPlaying = false;

// set frame-rate
this.frameRate = function(newfps) {
    if (!arguments.length) return fps;
    fps = newfps;
    delay = 1000 / fps;
    frame = -1;
    time = null;
};

// enable starting/pausing of the object
this.start = function() {
    if (!this.isPlaying) {
        this.isPlaying = true;
        tref = requestAnimationFrame(loop);
    }
};

this.pause = function() {
    if (this.isPlaying) {
        cancelAnimationFrame(tref);
        this.isPlaying = false;
        time = null;
        frame = -1;
    }
};

用法

它变得非常简单-现在,我们要做的就是通过设置回调函数和所需的帧速率来创建实例,如下所示:

var fc = new FpsCtrl(24, function(e) {
     // render each frame here
  });

然后开始(如果需要,这可以是默认行为):

fc.start();

就这样,所有逻辑都在内部处理。

演示版

var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0;
ctx.font = "20px sans-serif";

// update canvas with some information and animation
var fps = new FpsCtrl(12, function(e) {
	ctx.clearRect(0, 0, c.width, c.height);
	ctx.fillText("FPS: " + fps.frameRate() + 
                 " Frame: " + e.frame + 
                 " Time: " + (e.time - pTime).toFixed(1), 4, 30);
	pTime = e.time;
	var x = (pTime - mTime) * 0.1;
	if (x > c.width) mTime = pTime;
	ctx.fillRect(x, 50, 10, 10)
})

// start the loop
fps.start();

// UI
bState.onclick = function() {
	fps.isPlaying ? fps.pause() : fps.start();
};

sFPS.onchange = function() {
	fps.frameRate(+this.value)
};

function FpsCtrl(fps, callback) {

	var	delay = 1000 / fps,
		time = null,
		frame = -1,
		tref;

	function loop(timestamp) {
		if (time === null) time = timestamp;
		var seg = Math.floor((timestamp - time) / delay);
		if (seg > frame) {
			frame = seg;
			callback({
				time: timestamp,
				frame: frame
			})
		}
		tref = requestAnimationFrame(loop)
	}

	this.isPlaying = false;
	
	this.frameRate = function(newfps) {
		if (!arguments.length) return fps;
		fps = newfps;
		delay = 1000 / fps;
		frame = -1;
		time = null;
	};
	
	this.start = function() {
		if (!this.isPlaying) {
			this.isPlaying = true;
			tref = requestAnimationFrame(loop);
		}
	};
	
	this.pause = function() {
		if (this.isPlaying) {
			cancelAnimationFrame(tref);
			this.isPlaying = false;
			time = null;
			frame = -1;
		}
	};
}
body {font:16px sans-serif}
<label>Framerate: <select id=sFPS>
	<option>12</option>
	<option>15</option>
	<option>24</option>
	<option>25</option>
	<option>29.97</option>
	<option>30</option>
	<option>60</option>
</select></label><br>
<canvas id=c height=60></canvas><br>
<button id=bState>Start/Stop</button>

旧答案

的主要目的requestAnimationFrame是将更新同步到监视器的刷新率。这将需要您以监视器的FPS或以其为单位进行动画处理(例如,对于60 Hz的典型刷新率,为60、30、15 FPS)。

如果您想要更随意的FPS,那么使用rAF毫无意义,因为帧率永远不会与监视器的更新频率匹配(只是一处帧),根本无法为您提供流畅的动画(如对所有帧重新定时一样) ),您也可以使用setTimeoutsetInterval代替。

当您想要以不同的FPS播放视频然后刷新显示设备的视频时,这也是专业视频行业中众所周知的问题。已经使用了许多技术,例如帧混合和基于运动矢量的复杂重新定时重建中间帧,但是对于画布而言,这些技术不可用,并且结果将始终是抖动的视频。

var FPS = 24;  /// "silver screen"
var isPlaying = true;

function loop() {
    if (isPlaying) setTimeout(loop, 1000 / FPS);

    ... code for frame here
}

为什么我们把原因setTimeout 第一(以及为什么一些地方rAF在使用聚填充第一)的是,这将是为更准确setTimeout,当循环开始,这样不管多少时间剩下的代码将使用将立即排队事件(假设它不超过超时间隔)下一个调用将在其表示的间隔内进行(对于纯净rAF,这不是必需的,因为rAF在任何情况下都将尝试跳至下一帧)。

同样值得一提的是,将其放在第一位也可能会冒与一样堆积调用的风险setIntervalsetInterval可能会更准确一些。

你也可以使用setInterval,而不是循环做同样的。

var FPS = 29.97;   /// NTSC
var rememberMe = setInterval(loop, 1000 / FPS);

function loop() {

    ... code for frame here
}

并停止循环:

clearInterval(rememberMe);

为了降低选项卡变得模糊时的帧速率,您可以添加一个像这样的因素:

var isFocus = 1;
var FPS = 25;

function loop() {
    setTimeout(loop, 1000 / (isFocus * FPS)); /// note the change here

    ... code for frame here
}

window.onblur = function() {
    isFocus = 0.5; /// reduce FPS to half   
}

window.onfocus = function() {
    isFocus = 1; /// full FPS
}

这样您可以将FPS降低到1/4等。


4
在某些情况下,您不是试图匹配监视器的帧速率,而是以图像序列为例,例如丢帧。顺便说一句
很好的

3
限制requestAnimationFrame的最大原因之一是将某些代码的执行与浏览器的动画框架对齐。事情最终变得更加顺畅,尤其是如果您在每帧数据上运行某种逻辑时,例如使用音乐可视化程序。
克里斯·海豚

4
这很不好,因为主要用途requestAnimationFrame是同步DOM操作(读/写),因此不使用它会在访问DOM时影响性能,因为操作不会一起排队,并且会不必要地强制重新布局。
vsync

1
因为JavaScript运行单线程,并且在代码运行时不会触发超时事件,所以没有“调用堆积”的风险。因此,如果该函数花费的时间超过超时时间,则它几乎可以在任何时间以最快的速度运行,而浏览器仍会重绘并在调用之间触发其他超时。
dronus

我知道您说页面刷新的更新速度不能快于显示器上的fps限制。但是,是否可以通过触发页面重排来更快地刷新?相反,如果多页重排的速度比本机fps速率快,是否有可能不会注意到这些问题?
特拉维斯J

36

建议您将呼叫包装到requestAnimationFramesetTimeout。如果您setTimeout从请求动画帧的函数中调用,则会违反的目的requestAnimationFrame。但是,如果您requestAnimationFrame从内部调用,setTimeout则效果会很好:

var fps = 25
function animate() {
  setTimeout(function() {
    requestAnimationFrame(animate);
  }, 1000 / fps);
}

1
实际上,这似乎可以降低帧速率,而不用烹饪CPU。而且非常简单。干杯!

对于轻量级动画,这是一种很好的简单方法。但是,至少在某些设备上,它确实有点不同步。我在以前的一种引擎上使用了此技术。在事情变得复杂之前,它一直很好。最大的问题是当连接方向传感器时,它会滞后或跳动。后来我发现使用单独的setInterval并通过对象属性在传感器,setInterval框架和RAF框架之间传递更新,从而使传感器和RAF实时进行,而动画时间可以通过setInterval的属性更新来控制。
jdmayfield '18

最佳答案 !谢谢;)
538ROMEO '18年

我的显示器为60 FPS,如果我将var fps设置为60,则使用此代码只能得到大约50 FPS。我想将其速度降低到60,因为有些人拥有120 FPS显示器,但是我不想影响其他所有人。这是非常困难的。
柯蒂斯

您获得的FPS低于预期的原因是因为setTimeout可以在超过指定的延迟后执行回调。有很多可能的原因。而且每个循环都需要花费时间来设置新的计时器并在设置新的超时之前执行一些代码。您无法准确地做到这一点,应该始终考虑比预期的结果慢,但是只要您不知道结果会慢多少,尝试降低延迟也是不准确的。浏览器中的JS并不是那么准确。
pdepmcp '19

17

从理论上讲,这些都是好主意,直​​到深入为止。 问题是您不能在不同步RAF的情况下对其进行节流,这会破坏现有RAF的用途。 所以,你让它全速运行,并在单独的循环更新数据甚至一个单独的线程!

是的,我说了。您可以在浏览器中执行多线程JavaScript!

我知道有两种方法可以很好地解决这种问题,即使用更少的果汁并产生更少的热量。最终结果是准确的人机定时和机器效率。

抱歉,这有点罗word,但是这里...


方法1:通过setInterval更新数据,并通过RAF更新图形。

使用单独的setInterval来更新平移和旋转值,物理特性,碰撞等。将这些值保存在每个动画元素的对象中。将转换字符串分配给每个setInterval'frame'对象中的变量。将这些对象保留在数组中。将间隔设置为所需的fps(毫秒):ms =(1000 / fps)。这样可以保持稳定的时钟,无论RAF速度如何,在任何设备上都允许相同的fps。 不要在此处将变换分配给元素!

在requestAnimationFrame循环中,使用老式的for循环遍历数组-不要在这里使用较新的形式,它们很慢!

for(var i=0; i<sprite.length-1; i++){  rafUpdate(sprite[i]);  }

在rafUpdate函数中,从数组中的js对象及其元素ID获取转换字符串。您应该已经将“ sprite”元素附加到变量上,或者可以通过其他方式轻松访问,因此您不会浪费时间在RAF中“获取”它们。将它们保留在以其html id命名的对象中的效果很好。在将其放入SI或RAF之前将其设置好。

使用RAF 更新您的变换,使用3D变换(甚至用于2d),并将CSS设置为“ will-change:transform;”。会改变的元素。这样可以使您的转换尽可能地与本机刷新率同步,插入GPU,并告诉浏览器最集中的位置。

所以你应该有这样的伪代码...

// refs to elements to be transformed, kept in an array
var element = [
   mario: document.getElementById('mario'),
   luigi: document.getElementById('luigi')
   //...etc.
]

var sprite = [  // read/write this with SI.  read-only from RAF
   mario: { id: mario  ....physics data, id, and updated transform string (from SI) here  },
   luigi: {  id: luigi  .....same  }
   //...and so forth
] // also kept in an array (for efficient iteration)

//update one sprite js object
//data manipulation, CPU tasks for each sprite object
//(physics, collisions, and transform-string updates here.)
//pass the object (by reference).
var SIupdate = function(object){
  // get pos/rot and update with movement
  object.pos.x += object.mov.pos.x;  // example, motion along x axis
  // and so on for y and z movement
  // and xyz rotational motion, scripted scaling etc

  // build transform string ie
  object.transform =
   'translate3d('+
     object.pos.x+','+
     object.pos.y+','+
     object.pos.z+
   ') '+

   // assign rotations, order depends on purpose and set-up. 
   'rotationZ('+object.rot.z+') '+
   'rotationY('+object.rot.y+') '+
   'rotationX('+object.rot.x+') '+

   'scale3d('.... if desired
  ;  //...etc.  include 
}


var fps = 30; //desired controlled frame-rate


// CPU TASKS - SI psuedo-frame data manipulation
setInterval(function(){
  // update each objects data
  for(var i=0; i<sprite.length-1; i++){  SIupdate(sprite[i]);  }
},1000/fps); //  note ms = 1000/fps


// GPU TASKS - RAF callback, real frame graphics updates only
var rAf = function(){
  // update each objects graphics
  for(var i=0; i<sprite.length-1; i++){  rAF.update(sprite[i])  }
  window.requestAnimationFrame(rAF); // loop
}

// assign new transform to sprite's element, only if it's transform has changed.
rAF.update = function(object){     
  if(object.old_transform !== object.transform){
    element[object.id].style.transform = transform;
    object.old_transform = object.transform;
  }
} 

window.requestAnimationFrame(rAF); // begin RAF

这样可以使您对数据对象的更新和转换字符串同步到SI中所需的“帧”速率,并且将RAF中的实际转换分配同步到GPU刷新速率。因此,实际的图形更新仅在RAF中进行,而对数据的更改以及构建转换字符串在SI中进行,因此没有麻烦,而是“时间”以所需的帧速率流动。


流:

[setup js sprite objects and html element object references]

[setup RAF and SI single-object update functions]

[start SI at percieved/ideal frame-rate]
  [iterate through js objects, update data transform string for each]
  [loop back to SI]

[start RAF loop]
  [iterate through js objects, read object's transform string and assign it to it's html element]
  [loop back to RAF]

方法2.将SI放在网络工作者中。这是FAAAST,顺利!

与方法1相同,但将SI放入Web-worker。然后,它将在完全独立的线程上运行,从而使页面仅处理RAF和UI。将子画面数组作为“可传递对象”来回传递。这是buko快。克隆或序列化不需要花时间,但是这不像通过引用传递那样,因为另一侧的引用已被破坏,因此您需要使两侧都传递到另一侧,并且仅在存在时更新它们,进行排序就像在高中时与女友来回传递便条一样。

一次只能读写。只要他们检查它是否未定义以避免错误,就可以了。皇家空军是FAST,将立即将其踢回,然后经过一堆GPU帧,只是检查它是否已被送回。网络工作者中的SI大部分时间都将具有sprite数组,并将更新位置,运动和物理数据,并创建新的转换字符串,然后将其传递回页面中的RAF。

这是我知道的通过脚本对元素进行动画处理的最快方法。这两个函数将作为两个单独的程序在两个单独的线程上运行,并利用多核CPU的优势,而单个js脚本则不会。多线程javascript动画。

而且它可以平稳地运行,而不会出现抖动,但以实际指定的帧速率,几乎没有差异。


结果:

这两种方法中的任何一种都将确保您的脚本在任何PC,电话,平板电脑等上以相同的速度运行(当然,在设备和浏览器的功能范围内)。


附带一提-在方法1中,如果setInterval中的活动过多,则可能由于单线程异步而使RAF变慢。您可以减轻这种破坏活动的可能性,而不仅仅是在SI框架上,所以异步会将控制更快地传递回RAF。请记住,RAF以最大帧速率运行,但是将图形更改与显示器同步,因此可以跳过一些RAF帧-只要您跳过的帧不会超过SI帧,就不会出现抖动。
jdmayfield '18

方法2更加健壮,因为它实际上是对两个循环执行多任务处理,而不是通过异步来回切换,但是您仍然希望避免SI帧花费比所需帧速率更长的时间,因此拆分SI活动可能仍然是如果正在进行大量数据处理,则需要多个SI帧来完成。
jdmayfield '18

值得一提的是,我认为值得一提的是,像这样运行配对循环实际上是在Chromes DevTools中注册的,GPU正在以setInterval循环中指定的帧速率运行!看起来只有出现图形变化的RAF帧才被FPS计入帧。因此,仅涉及非图形工作甚至空白循环的RAF帧就GPU而言并不重要。我觉得这很有趣,可以作为进一步研究的起点。
jdmayfield '18

我认为该解决方案存在一个问题,即当rAF挂起时,它会继续运行,例如,因为用户切换到了另一个选项卡。
N4ppeL

1
PS我做了一些阅读,似乎大多数浏览器无论如何都会在后台选项卡中将定时事件限制为每秒一次(可能也应该以某种方式处理)。如果您仍然想解决该问题并在看不见时完全暂停,则似乎有此visibilitychange事件。
N4ppeL

3

如何轻松调节到特定的FPS:

// timestamps are ms passed since document creation.
// lastTimestamp can be initialized to 0, if main loop is executed immediately
var lastTimestamp = 0,
    maxFPS = 30,
    timestep = 1000 / maxFPS; // ms for each frame

function main(timestamp) {
    window.requestAnimationFrame(main);

    // skip if timestep ms hasn't passed since last frame
    if (timestamp - lastTimestamp < timestep) return;

    lastTimestamp = timestamp;

    // draw frame here
}

window.requestAnimationFrame(main);

资料来源:Isaac Sukin对JavaScript游戏循环和计时的详细说明


1
如果我的显示器以60 FPS运行,并且我希望我的游戏以58 FPS运行(我将maxFPS = 58设置),则它将使其以30 FPS运行,因为它将跳过第二帧。
柯蒂斯

是的,我也尝试过这个。我选择不实际限制RAF本身-仅通过setTimeout更新更改。根据DevTools中的读数,至少在Chrome中,这会使有效fps以setTimeouts的速度运行。当然,它只能以视频卡的速度和监视器刷新率来更新实际的视频帧,但是这种方法似乎可以使最少的垃圾邮件操作,从而实现最平滑的“表观” fps控制,这正是我想要的。
jdmayfield

由于我与RAF分开跟踪JS对象中的所有运动,因此无论RAF或setTimeout如何,动画逻辑,碰撞检测或您需要的任何内容都以感知上一致的速率运行,而无需多加一点数学。
jdmayfield '19

2

跳过requestAnimationFrame导致自定义fps动画不平滑(所需)。

// Input/output DOM elements
var $results = $("#results");
var $fps = $("#fps");
var $period = $("#period");

// Array of FPS samples for graphing

// Animation state/parameters
var fpsInterval, lastDrawTime, frameCount_timed, frameCount, lastSampleTime, 
		currentFps=0, currentFps_timed=0;
var intervalID, requestID;

// Setup canvas being animated
var canvas = document.getElementById("c");
var canvas_timed = document.getElementById("c2");
canvas_timed.width = canvas.width = 300;
canvas_timed.height = canvas.height = 300;
var ctx = canvas.getContext("2d");
var ctx2 = canvas_timed.getContext("2d");


// Setup input event handlers

$fps.on('click change keyup', function() {
    if (this.value > 0) {
        fpsInterval = 1000 / +this.value;
    }
});

$period.on('click change keyup', function() {
    if (this.value > 0) {
        if (intervalID) {
            clearInterval(intervalID);
        }
        intervalID = setInterval(sampleFps, +this.value);
    }
});


function startAnimating(fps, sampleFreq) {

    ctx.fillStyle = ctx2.fillStyle = "#000";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx2.fillRect(0, 0, canvas.width, canvas.height);
    ctx2.font = ctx.font = "32px sans";
    
    fpsInterval = 1000 / fps;
    lastDrawTime = performance.now();
    lastSampleTime = lastDrawTime;
    frameCount = 0;
    frameCount_timed = 0;
    animate();
    
    intervalID = setInterval(sampleFps, sampleFreq);
		animate_timed()
}

function sampleFps() {
    // sample FPS
    var now = performance.now();
    if (frameCount > 0) {
        currentFps =
            (frameCount / (now - lastSampleTime) * 1000).toFixed(2);
        currentFps_timed =
            (frameCount_timed / (now - lastSampleTime) * 1000).toFixed(2);
        $results.text(currentFps + " | " + currentFps_timed);
        
        frameCount = 0;
        frameCount_timed = 0;
    }
    lastSampleTime = now;
}

function drawNextFrame(now, canvas, ctx, fpsCount) {
    // Just draw an oscillating seconds-hand
    
    var length = Math.min(canvas.width, canvas.height) / 2.1;
    var step = 15000;
    var theta = (now % step) / step * 2 * Math.PI;

    var xCenter = canvas.width / 2;
    var yCenter = canvas.height / 2;
    
    var x = xCenter + length * Math.cos(theta);
    var y = yCenter + length * Math.sin(theta);
    
    ctx.beginPath();
    ctx.moveTo(xCenter, yCenter);
    ctx.lineTo(x, y);
  	ctx.fillStyle = ctx.strokeStyle = 'white';
    ctx.stroke();
    
    var theta2 = theta + 3.14/6;
    
    ctx.beginPath();
    ctx.moveTo(xCenter, yCenter);
    ctx.lineTo(x, y);
    ctx.arc(xCenter, yCenter, length*2, theta, theta2);

    ctx.fillStyle = "rgba(0,0,0,.1)"
    ctx.fill();
    
    ctx.fillStyle = "#000";
    ctx.fillRect(0,0,100,30);
    
    ctx.fillStyle = "#080";
    ctx.fillText(fpsCount,10,30);
}

// redraw second canvas each fpsInterval (1000/fps)
function animate_timed() {
    frameCount_timed++;
    drawNextFrame( performance.now(), canvas_timed, ctx2, currentFps_timed);
    
    setTimeout(animate_timed, fpsInterval);
}

function animate(now) {
    // request another frame
    requestAnimationFrame(animate);
    
    // calc elapsed time since last loop
    var elapsed = now - lastDrawTime;

    // if enough time has elapsed, draw the next frame
    if (elapsed > fpsInterval) {
        // Get ready for next frame by setting lastDrawTime=now, but...
        // Also, adjust for fpsInterval not being multiple of 16.67
        lastDrawTime = now - (elapsed % fpsInterval);

        frameCount++;
    		drawNextFrame(now, canvas, ctx, currentFps);
    }
}
startAnimating(+$fps.val(), +$period.val());
input{
  width:100px;
}
#tvs{
  color:red;
  padding:0px 25px;
}
H3{
  font-weight:400;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3>requestAnimationFrame skipping <span id="tvs">vs.</span> setTimeout() redraw</h3>
<div>
    <input id="fps" type="number" value="33"/> FPS:
    <span id="results"></span>
</div>
<div>
    <input id="period" type="number" value="1000"/> Sample period (fps, ms)
</div>
<canvas id="c"></canvas><canvas id="c2"></canvas>

@tavnab的原始代码。


2
var time = 0;
var time_framerate = 1000; //in milliseconds

function animate(timestamp) {
  if(timestamp > time + time_framerate) {
    time = timestamp;    

    //your code
  }

  window.requestAnimationFrame(animate);
}

请添加一些句子来解释您的代码在做什么,以便您获得更多的支持。
模糊分析

1

我总是以非常简单的方式做到这一点,而不会弄乱时间戳:

var fps, eachNthFrame, frameCount;

fps = 30;

//This variable specifies how many frames should be skipped.
//If it is 1 then no frames are skipped. If it is 2, one frame 
//is skipped so "eachSecondFrame" is renderd.
eachNthFrame = Math.round((1000 / fps) / 16.66);

//This variable is the number of the current frame. It is set to eachNthFrame so that the 
//first frame will be renderd.
frameCount = eachNthFrame;

requestAnimationFrame(frame);

//I think the rest is self-explanatory
fucntion frame() {
  if (frameCount == eachNthFrame) {
    frameCount = 0;
    animate();
  }
  frameCount++;
  requestAnimationFrame(frame);
}

1
如果您的显示器为120 fps,这将运行得太快。
柯蒂斯

0

我找到的一个很好的解释是:CreativeJS.com,将setTimeou包装在调用传递给requestAnimationFrame的函数中。我对“ plain” requestionAnimationFrame的关注是:“如果我只希望它每秒进行三次动画,那会怎样?” 即使使用requestAnimationFrame(与setTimeout相对)也是仍然浪费(一些)“能量”(意味着浏览器代码正在做某事,并且可能使系统速度变慢)为60或120,或者每秒多次,例如而不是每秒仅2到3次(可能需要)。

出于这个原因,大多数情况下,我总是在关闭 JavaScript的情况运行浏览器。但是,我使用的是优胜美地10.10.3,我认为它存在某种计时器问题-至少在我的旧系统上(相对较旧-表示2011)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.