อัปเดต 2016/6
ปัญหาในการควบคุมอัตราเฟรมคือหน้าจอมีอัตราการอัปเดตคงที่โดยทั่วไปคือ 60 FPS
หากเราต้องการ 24 FPS เราจะไม่ได้ 24 fps ที่แท้จริงบนหน้าจอเราสามารถตั้งเวลาได้เช่นนี้ แต่ไม่แสดงเนื่องจากจอภาพสามารถแสดงเฟรมที่ซิงค์ได้ที่ 15 fps, 30 fps หรือ 60 fps เท่านั้น (จอภาพบางจอ 120 fps ).
อย่างไรก็ตามเพื่อจุดประสงค์ด้านเวลาเราสามารถคำนวณและอัปเดตเมื่อเป็นไปได้
คุณสามารถสร้างตรรกะทั้งหมดสำหรับการควบคุมอัตราเฟรมโดยการห่อหุ้มการคำนวณและการเรียกกลับลงในวัตถุ:
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;
}
};
การใช้งาน
มันกลายเป็นเรื่องง่ายมาก - ตอนนี้สิ่งที่เราต้องทำก็คือการสร้างอินสแตนซ์โดยตั้งค่าฟังก์ชันเรียกกลับและอัตราเฟรมที่ต้องการดังนี้:
var fc = new FpsCtrl(24, function(e) {
});
จากนั้นเริ่มต้น (ซึ่งอาจเป็นพฤติกรรมเริ่มต้นหากต้องการ):
fc.start();
เพียงเท่านี้ตรรกะทั้งหมดจะถูกจัดการภายใน
การสาธิต
var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0;
ctx.font = "20px sans-serif";
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)
})
fps.start();
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, 30, 15 FPS สำหรับอัตราการรีเฟรชทั่วไปที่ 60 Hz)
หากคุณต้องการ FPS ตามอำเภอใจมากขึ้นไม่มีจุดใดที่จะใช้ rAF เนื่องจากอัตราเฟรมจะไม่ตรงกับความถี่ในการอัปเดตของจอภาพอีกต่อไป (เพียงแค่เฟรมตรงนี้และตรงนั้น) ซึ่งไม่สามารถให้ภาพเคลื่อนไหวที่ราบรื่นแก่คุณได้ (เช่นเดียวกับการกำหนดเวลาเฟรมใหม่ทั้งหมด ) และคุณสามารถใช้setTimeout
หรือใช้setInterval
แทนได้เช่นกัน
นี่เป็นปัญหาที่รู้จักกันดีในอุตสาหกรรมวิดีโอระดับมืออาชีพเมื่อคุณต้องการเล่นวิดีโอที่ FPS อื่นจากนั้นอุปกรณ์จะแสดงการรีเฟรชที่ มีการใช้เทคนิคหลายอย่างเช่นการผสมเฟรมและการกำหนดเวลาใหม่ที่ซับซ้อนในการสร้างเฟรมขั้นกลางใหม่โดยอาศัยเวกเตอร์การเคลื่อนไหว แต่เทคนิคเหล่านี้ใช้ผ้าใบไม่ได้และผลลัพธ์จะเป็นวิดีโอที่กระตุกเสมอ
var FPS = 24;
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;
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));
... code for frame here
}
window.onblur = function() {
isFocus = 0.5;
}
window.onfocus = function() {
isFocus = 1;
}
ด้วยวิธีนี้คุณสามารถลด FPS เป็น 1/4 เป็นต้น
requestAnimationFrame
คือ (ตามชื่อที่แนะนำ) ขอเฟรมแอนิเมชั่นเมื่อจำเป็นเท่านั้น สมมติว่าคุณแสดงผ้าใบสีดำแบบคงที่คุณควรได้ 0 fps เพราะไม่จำเป็นต้องใช้เฟรมใหม่ แต่ถ้าคุณกำลังแสดงภาพเคลื่อนไหวที่ต้องใช้ 60fps คุณก็ควรได้รับเช่นกันrAF
เพียงแค่อนุญาตให้ "ข้าม" เฟรมที่ไร้ประโยชน์แล้วบันทึก CPU