为了账号安全,请及时绑定邮箱和手机立即绑定

使用requestAnimationFrame控制fps?

/ 猿问

使用requestAnimationFrame控制fps?

这似乎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()只是一些代码,它根据计数器变量确定半径,然后绘制一个圆。


查看完整描述

3 回答

?
MMTTMM

如何将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


    }

}


查看完整回答
反对 回复 2019-08-30
?
回首忆惘然

限制帧速率的问题是屏幕具有恒定的更新速率,通常为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是没有意义的,因为帧速率永远不会与显示器的更新频率相匹配(只是这里和那里的一个帧),它根本无法给你一个平滑的动画(就像所有的帧重新定时一样) )你也可以使用setTimeout或setInterval代替。


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


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

var isPlaying = true;


function loop() {

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


    ... code for frame here

}

我们setTimeout 首先放置的原因(以及为什么某些地方rAF在使用poly-fill时首先放置)的原因是,这将更加准确,因为setTimeout当循环开始时,将立即对事件进行排队,这样无论剩余代码将使用多长时间(假设它没有超过超时间隔)下一次调用将是它所代表的间隔(对于纯rAF,这不是必需的,因为rAF将尝试在任何情况下跳转到下一帧)。


另外值得注意的是,将它放在第一位也会冒着堆叠的风险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等。


查看完整回答
反对 回复 2019-08-30
?
慕田峪4524236

我建议把你的电话换成requestAnimationFrame一个setTimeout。如果你setTimeout从你请求动画帧的函数中调用,那么你就失去了目的requestAnimationFrame。但如果你requestAnimationFrame从内部打电话setTimeout顺利工作:


var fps = 25

function animate() {

  setTimeout(function() {

    requestAnimationFrame(animate);

  }, 1000 / fps);

}


查看完整回答
反对 回复 2019-08-30

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信