更新 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>
旧答案
的主要目的是将更新同步到监视器的刷新率。这将要求您以监视器的FPS或其系数(即60,30,15 FPS,典型刷新率为60 Hz)进行动画处理。requestAnimationFrame
如果你想要一个更任意的FPS,那么使用rAF是没有意义的,因为帧速率无论如何都不会与显示器的更新频率相匹配(只是这里和那里的一帧),这根本无法给你一个平滑的动画(就像所有帧重新计时一样),你也可以使用或代替。setTimeout
setInterval
这也是专业视频行业中众所周知的问题,当您想要以不同的FPS播放视频时,然后显示它的设备会刷新。已经使用了许多技术,例如帧混合和基于运动矢量的复杂重新定时重新构建中间帧,但是对于画布,这些技术不可用,结果将始终是抖动的视频。
var FPS = 24; /// "silver screen"
var isPlaying = true;
function loop() {
if (isPlaying) setTimeout(loop, 1000 / FPS);
... code for frame here
}
我们放在第一位(以及为什么在使用多边形填充时将某个位置放在第一位)的原因是,这将更准确,因为当循环开始时,它将立即对事件进行排队,因此无论剩余代码将使用多少时间(前提是它不超过超时间隔),下一个调用都将位于它所代表的间隔(对于纯rAF,这不是必需的,因为rAF将尝试在任何情况下都跳到下一帧)。setTimeout
rAF
setTimeout
另外值得注意的是,将其放在第一位也会冒着调用堆积的风险。 对于此用途可能稍微更准确。setInterval
setInterval
你可以在循环之外使用来做同样的事情。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等。