Frame skip in requestAnimationFrame
by bc_rikko
HTML
<div>
<label><input type="checkbox" id="skip" checked>フレームスキップする</label>
<button type="button" id="start">start</button>
<button type="button" id="stop">stop</button>
</div>
<canvas id="game" width="640" height="100"></canvas>
CSS
canvas#game {
border: 4px solid;
}
JavaScript
const $ = selector => document.querySelector(selector);
// Debug用
const sleep = msec => new Promise((resolve, reject) => setTimeout(resolve, msec));
class Item {
constructor({ x, y }) {
this.x = x;
this.y = y;
this.w = 40;
this.h = 40;
this.way = 1;
}
draw(game) {
game.ctx.save();
game.ctx.fillStyle = 'red';
game.ctx.fillRect(this.x, this.y, this.w, this.h);
game.ctx.restore();
}
update(game) {
this.x += 10 * this.way;
if (game.w <= this.x + this.w) {
this.way = -1;
} else if (this.x < 0) {
this.way = 1;
}
}
}
class Game {
constructor(el) {
this.canvas = document.querySelector(el);
this.ctx = this.canvas.getContext('2d');
this.w = this.canvas.width;
this.h = this.canvas.height;
this.isRunning = false;
this.items = [];
this.currentFrame = 0;
this.fps = 60.0;
this.now = 0;
}
push(item) {
this.items.push(item);
}
start() {
if (!this.isRunning) {
this.isRunning = true;
this.reqId = window.requestAnimationFrame(this.render.bind(this));
}
}
stop() {
this.isRunning = false;
window.cancelAnimationFrame(this.reqId);
}
async render(timestamp) {
let delta = Math.floor(timestamp - this.now);
if ($('#skip').checked) {
// 処理落ちした分だけupdateする(描画処理は行わない)
while (delta >= 0) {
for (let item of this.items) {
item.update(this);
}
delta -= 1000/ this.fps;
this.currentFrame++;
}
} else {
// フレームスキップしない
for (let item of this.items) {
item.update(this);
}
this.currentFrame++;
}
this.ctx.clearRect(0, 0, this.w, this.h);
for (let item of this.items) {
item.draw(this);
}
// DEBUG:250ms(約16フレーム)処理落ちさせる
if (this.currentFrame % this.fps === 0) {
await sleep(250);
}
this.now = timestamp;
this.reqId =...