태그 보관물: canvas

canvas

requestAnimationFrame으로 fps를 제어합니까? rAF가 제공하는 최적화를

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 ()는 카운터 변수를 기반으로 반경을 결정한 다음 원을 그리는 코드입니다.



답변

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

    }
}

답변

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 등으로 줄일 수 있습니다.


답변

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

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

답변

이것들은 당신이 깊이 갈 때까지 이론적으로 좋은 아이디어입니다. 문제는 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, 휴대 전화, 태블릿 등에서 기기와 브라우저의 기능 내에서 스크립트가 동일한 속도로 실행됩니다.


답변

특정 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 게임 루프 및 타이밍에 대한 자세한 설명


답변

건너 뛰기 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의 원본 코드.


답변

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

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

    //your code
  }

  window.requestAnimationFrame(animate);
}