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은 (이름 종류의 제안으로) 필요할 때만 애니메이션 프레임을 요청하는 것입니다. 정적 블랙 캔버스를 표시한다고 가정하면 새 프레임이 필요하지 않으므로 0fps를 가져와야합니다. 그러나 60fps가 필요한 애니메이션을 표시하는 경우에도 해당 정보를 가져와야합니다. rAF쓸모없는 프레임을 "건너 뛰고"CPU를 절약 할 수 있습니다.
maxdec

비활성 탭에서도 setInterval이 작동하지 않습니다.
ViliusL

이 코드는 90hz 디스플레이 대 60hz 디스플레이 대 144hz 디스플레이에서 다르게 실행됩니다.
manthrax

답변:


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();
}

그리고이 코드는 지정된 FPS에서 그리는 실제 requestAnimationFrame 루프입니다.

// 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

13
좋은 데모-받아 들여야합니다. 여기서는 Date.now () 대신 window.performance.now () 사용을 시연하기 위해 바이올린을 포크했습니다. 이것은 rAF가 이미 수신 한 고해상도 타임 스탬프와 잘 어울리므로 콜백 안에서 Date.now ()를 호출 할 필요가 없습니다. jsfiddle.net/chicagogrooves/nRpVD/2
Dean Radcliffe

2
새로운 rAF 타임 스탬프 기능을 사용하여 업데이트 된 링크에 감사드립니다. 새로운 rAF 타임 스탬프는 유용한 인프라를 추가하고 Date.now보다 더 정확합니다.
markE

13
이것은 정말 좋은 데모이며, 나 자신을 만들도록 영감을주었습니다 ( JSFiddle ). 주요 차이점은 Date 대신 rAF (Dean의 데모와 같은)를 사용하여 대상 프레임 속도를 동적으로 조정하는 컨트롤 추가, 애니메이션과 별도의 간격으로 프레임 속도 샘플링 및 기록 프레임 속도 그래프를 추가하는 것입니다.
tavnab

1
프레임을 건너 뛸 때만 제어 할 수 있습니다. 60fps 모니터는 항상 16ms 간격으로 그립니다. 예를 들어 게임을 50fps로 실행하려면 6 번째 프레임마다 건너 뛰기를 원합니다. 20ms (1000/50)가 경과했는지 확인하고 16ms 만 경과하지 않았으므로 프레임을 건너 뛰고 그린 후 32ms가 경과하여 그리기 및 재설정합니다. 그러나 프레임의 절반을 건너 뛰고 30fps로 실행합니다. 재설정 할 때 지난 시간에 12ms를 너무 오래 기다렸다는 것을 기억하십시오. 다음 프레임은 또 다른 16ms가 지나가지만 16 + 12 = 28ms로 계산하여 다시 그리면서 8ms를 너무 오래 기다렸습니다
Curtis

47

2016/6 업데이트

프레임 속도를 조절하는 문제는 화면에 일정한 업데이트 속도 (일반적으로 60FPS)가 있다는 것입니다.

24 FPS를 원한다면 화면에서 실제 24fps를 절대 얻지 못하지만 모니터는 15fps, 30fps 또는 60fps에서 동기화 된 프레임 만 표시 할 수 있으므로 표시 할 수는 없습니다 (일부 모니터도 120fps) ).

그러나 타이밍을 위해 가능한 경우 계산하고 업데이트 할 수 있습니다.

계산 및 콜백을 객체에 캡슐화하여 프레임 속도를 제어하기위한 모든 논리를 작성할 수 있습니다.

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 또는 그 요소 (즉, 60Hz에서 일반적인 재생률의 경우 60, 30, 15FPS)에서 애니메이션을 적용해야합니다.

좀 더 임의의 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폴리 채우기가 사용될 때 어떤 장소가 먼저 배치되는 이유 )는 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 등으로 줄일 수 있습니다.


4
경우에 따라 모니터 프레임 속도를 맞추려고하지 않고 이미지 시퀀스 (예 : 프레임 삭제)에서 일치시키지 않습니다. 훌륭한 설명 btw
sidonaldson

3
requestAnimationFrame으로 조절해야하는 가장 큰 이유 중 하나는 브라우저의 애니메이션 프레임과 일부 코드의 실행을 정렬하는 것입니다. 특히 음악 비주얼 라이저와 같이 매 프레임마다 데이터에서 일부 로직을 실행하는 경우 상황이 훨씬 순조롭게 진행됩니다.
Chris Dolphin

4
requestAnimationFrameDOM 작업을 동기화 (읽기 / 쓰기) 하는 것이 주된 용도 이므로 사용하지 않으면 DOM에 액세스 할 때 성능이 저하됩니다. 작업이 함께 대기하도록 대기하지 않고 레이아웃을 불필요하게 다시 그리기 때문입니다.
vsync

1
JavaScript가 단일 스레드를 실행하고 코드가 실행되는 동안 시간 초과 이벤트가 트리거되지 않으므로 "호출 누적"의 위험이 없습니다. 따라서 함수가 시간 초과보다 오래 걸리면 브라우저는 최대한 빨리 실행되지만 브라우저는 여전히 다시 그리기를 수행하고 호출 사이의 다른 시간 초과를 트리거합니다.
dronus

페이지 새로 고침을 디스플레이의 fps 제한보다 빠르게 업데이트 할 수 없다는 것을 알고 있습니다. 그러나 페이지 리플 로우를 트리거하여 더 빠르게 새로 고칠 수 있습니까? 반대로, 기본 fps 속도보다 빠른 속도로 여러 페이지 리플 로우가 발생하면이를 알 수 없습니까?
트래비스 J

36

에 전화를 줄 것을 제안 requestAnimationFrame합니다 setTimeout. setTimeout애니메이션 프레임을 요청한 함수 내에서 호출 하면의 목적을 잃게 requestAnimationFrame됩니다. 그러나 requestAnimationFrame내부에서 전화하면 setTimeout원활하게 작동합니다.

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

1
이것은 실제로 프레임 속도를 낮추고 CPU를 요리하지 않는 것으로 보입니다. 그리고 너무 간단합니다. 건배!
phocks September

이것은 가벼운 애니메이션을위한 훌륭하고 간단한 방법입니다. 적어도 일부 장치에서는 동기화가 약간 이루어지지 않습니다. 이전 엔진 중 하나에서이 기술을 사용했습니다. 일이 복잡해질 때까지 잘 작동했습니다. 가장 큰 문제는 방향 센서에 연결될 때 뒤쳐 지거나 튀어 나오는 것입니다. 나중에 별도의 setInterval을 사용하고 객체 속성을 통해 센서, setInterval 프레임 및 RAF 프레임 간의 업데이트 통신을 통해 센서와 RAF가 실시간으로 이동하는 반면 애니메이션 시간은 setInterval의 속성 업데이트를 통해 제어 할 수 있음을 알았습니다.
jdmayfield

최고의 답변! 감사합니다;)
538ROMEO

내 모니터는 60 FPS입니다. var fps = 60을 설정하면이 코드를 사용하여 약 50 FPS 만 얻을 수 있습니다. 일부 사람들은 120 대의 FPS 모니터를 가지고 있기 때문에 60으로 늦추고 싶지만 다른 사람들에게 영향을 미치고 싶지 않습니다. 이것은 놀랍게도 어렵다.
커티스

예상보다 낮은 FPS를 얻는 이유는 setTimeout이 지정된 지연 이상으로 콜백을 실행할 수 있기 때문입니다. 여기에는 여러 가지 이유가 있습니다. 그리고 매 루프마다 새로운 타이머를 설정하고 새로운 타임 아웃을 설정하기 전에 코드를 실행하는 데 시간이 걸립니다. 이를 정확하게 할 수있는 방법은 없습니다. 항상 예상 결과보다 느리게 생각해야하지만 얼마나 느려질 지 모르면 지연을 줄이려는 시도도 정확하지 않습니다. 브라우저의 JS는 그다지 정확하지 않습니다.
pdepmcp

17

이것들은 당신이 깊이 갈 때까지 이론적으로 좋은 아이디어입니다. 문제는 RAF를 비 동기화하지 않고 RAF를 조절할 수 없어 기존의 목적을 크게 상실한다는 것입니다. 당신은 최고 속도로 실행하고 별도의 루프에서 데이터를 업데이트 할 수 있도록 , 또는 별도의 스레드!

그렇습니다. 브라우저에서 멀티 스레드 JavaScript를 수행 있습니다 !

주스를 사용하지 않고 열을 적게 발생 시키므로 잔해없이 매우 잘 작동하는 두 가지 방법이 있습니다. 정확한 인간 규모의 타이밍과 기계 효율성이 그 결과입니다.

이것이 조금 장황하다면 사과하지만 여기 간다 ...


방법 1 : setInterval을 통해 데이터를 업데이트하고 RAF를 통해 그래픽을 업데이트합니다.

변환 및 회전 값, 물리, 충돌 등을 업데이트하려면 별도의 setInterval을 사용하십시오. 각 애니메이션 요소의 오브젝트에 해당 값을 유지하십시오. 변환 문자열을 각 setInterval 'frame'객체의 변수에 지정하십시오. 이 객체들을 배열로 유지하십시오. ms 단위로 원하는 fps 간격을 설정하십시오 : ms = (1000 / fps). 이는 RAF 속도에 관계없이 모든 장치에서 동일한 fps를 허용하는 안정적인 클럭을 유지합니다. 여기에 요소에 변환을 할당하지 마십시오!

requestAnimationFrame 루프에서 구식 for 루프를 사용하여 배열을 반복합니다. 여기에서 최신 양식을 사용하지 마십시오. 느립니다!

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

rafUpdate 함수에서 배열의 js 객체 및 해당 요소 ID에서 변환 문자열을 가져옵니다. '스프라이트'요소를 변수에 첨부했거나 다른 방법을 통해 쉽게 액세스 할 수 있어야하므로 RAF에서 'get'하는 데 시간을 낭비하지 않아도됩니다. 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를 넣습니다. 그런 다음 완전히 별도의 스레드에서 실행되어 RAF 및 UI 만 처리하도록 페이지를 남겨 둡니다. 스프라이트 배열을 '전송 가능한 객체'로 앞뒤로 전달하십시오. 이것은 부코 빠릅니다. 복제하거나 직렬화하는 데 시간이 걸리지 않지만 다른 쪽의 참조가 파괴된다는 점에서 참조로 전달하는 것은 아닙니다. 따라서 양쪽이 다른쪽으로 전달되고 존재하는 경우에만 업데이트해야합니다. 고등학교에서 여자 친구와 메모를주고받는 것 같은

한 번에 한 사람 만 읽고 쓸 수 있습니다. 오류를 피하기 위해 정의되지 않은 경우 확인하는 한 괜찮습니다. RAF는 빠르며 즉시 되돌려 보내지며 GPU 프레임을 통해 다시 전송되었는지 확인합니다. 웹 워커의 SI는 대부분 스프라이트 배열을 가지며 위치, 이동 및 물리 데이터를 업데이트하고 새 변환 문자열을 만든 다음 페이지의 RAF로 다시 전달합니다.

이것은 스크립트를 통해 요소를 애니메이션하는 가장 빠른 방법입니다. 두 함수는 두 개의 개별 스레드에서 두 개의 개별 프로그램으로 실행되며 단일 js 스크립트가하지 않는 방식으로 멀티 코어 CPU를 활용합니다. 멀티 스레드 자바 스크립트 애니메이션.

그리고 잔잔함없이 매끄럽게 진행되지만 실제로 지정된 프레임 속도로 발산이 거의 없습니다.


결과:

이 두 가지 방법 중 하나를 사용하면 PC, 휴대 전화, 태블릿 등에서 기기와 브라우저의 기능 내에서 스크립트가 동일한 속도로 실행됩니다.


참고로-방법 1에서 setInterval에 활동이 너무 많으면 단일 스레드 비동기로 인해 RAF가 느려질 수 있습니다. SI 프레임 이상에서 이러한 활동을 해소 할 수 있으므로 비동기는 제어를 RAF로 더 빨리 전달합니다. RAF는 최대 프레임 속도로 진행되지만 그래픽 변경 사항을 디스플레이와 동기화하므로 SI 프레임 이상을 건너 뛰지 않는 한 RAF 프레임을 건너 뛰어도 괜찮습니다.
jdmayfield

방법 2는 실제로 비동기를 통해 앞뒤로 전환하지 않고 두 루프를 멀티 태스킹하기 때문에 더 강력하지만 SI 프레임을 원하는 프레임 속도보다 오래 걸리지 않기를 원하므로 SI 활동을 분할 할 수 있습니다. 많은 데이터 조작이 진행되는 경우 완료하는 데 둘 이상의 SI 프레임이 필요합니다.
jdmayfield

관심있는 참고로 이와 같이 페어 루프를 실행하면 실제로 GPU가 setInterval 루프에 지정된 프레임 속도로 실행되고 있음을 Chrome DevTools에 등록한다고 언급 할 가치가 있다고 생각했습니다! 그래픽 변경이 발생하는 RAF 프레임 만 FPS 미터에 의해 프레임으로 계산됩니다. 따라서 그래픽이 아닌 작업 또는 빈 루프 만있는 RAF 프레임은 GPU에 관한 한 계산되지 않습니다. 나는 이것이 더 흥미로운 연구의 출발점으로 흥미 롭다는 것을 안다.
jdmayfield

이 솔루션에는 rAF가 중단 될 때 (예 : 사용자가 다른 탭으로 전환했기 때문에) 계속 실행되는 문제가 있다고 생각합니다.
N4ppeL

1
추신 : 나는 약간의 독서를했고 대부분의 브라우저는 어쨌든 백그라운드 탭에서 시간 초과 이벤트를 초당 한 번으로 제한하는 것처럼 보입니다 (어쩌면 어떤 방식으로도 처리해야 함). 여전히 문제를 해결하고 보이지 않을 때 완전히 일시 중지하려면 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
모니터가 60FPS에서 실행되고 게임을 58FPS에서 실행하려면 maxFPS = 58을 설정하면 매 두 번째 프레임을 건너 뛰기 때문에 30FPS에서 실행됩니다.
커티스

네, 이것도 시도했습니다. RAF 자체를 실제로 조절하지 않기로 선택합니다. 변경 사항 만 setTimeout에 의해 업데이트됩니다. Chrome에서는 최소한 DevTools의 판독 값에 따라 유효 fps가 setTimeouts 속도로 실행됩니다. 물론 비디오 카드의 속도로 실제 비디오 프레임 만 업데이트하고 새로 고침 빈도를 모니터링 할 수 있지만이 방법은 가장 덜 잔인한 동작으로 작동하는 것 같습니다. 따라서 가장 부드러운 "명확한"fps 컨트롤을 사용합니다.
jdmayfield

JS 객체의 모든 모션을 RAF와 별도로 추적하기 때문에 애니메이션 로직, 충돌 감지 또는 필요한 모든 것을 약간의 추가 수학으로 RAF 또는 setTimeout에 관계없이 지각 적으로 일관된 속도로 실행합니다.
jdmayfield 2018

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
모니터가 120fps 인 경우 너무 빠르게 실행됩니다.
커티스

0

다음은 내가 찾은 좋은 설명입니다. CreativeJS.com , requestAnimationFrame에 전달 된 함수 내에서 setTimeou) 호출을 래핑합니다. "plain"requestionAnimationFrame에 대한 나의 관심은 " 초당 3 번만 애니메이션을 만들 려면 어떻게해야 할까요?" requestAnimationFrame (setTimeout과 반대) 에서도 여전히 "에너지"를 낭비합니다 (브라우저 코드가 무언가를하고 시스템을 느리게 할 수 있음을 의미합니다). (원하는대로) 1 초에 2 ~ 3 회만 반대합니다.

대부분의 경우이 이유 때문에 JavaScript를 사용하여 브라우저를 본격적으로 해제 했습니다. 그러나 Yosemite 10.10.3을 사용하고 있으며 적어도 오래된 시스템 (상대적으로 오래된 2011 년)에는 타이머 문제가 있다고 생각합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.