With devices (like the new iPad) featuring displays over 60Hz, there is a good chance that browsers will be running at rates greater than 60Hz as well. That’s great for everyone except those of us who used requestAnimationFrame thinking that rates will stay fixed at 60Hz,
To force a frame rate, check out the following snippet:
var currentTime;
var elapsedTime;
var startTime;
// set the frame rate you want!
var fps = 60;
var interval = 1000 / fps;
function loop() {
currentTime = performance.now();
elapsedTime = currentTime - startTime;
if (elapsedTime > interval) {
// this seems weird, but it is done to account for
// the time it takes to display a frame
startTime = currentTime - (elapsedTime % interval);
//
// Your animation code goes below!
//
}
requestAnimationFrame(loop);
}
// Start the timer!
function start() {
startTime = performance.now();
requestAnimationFrame(loop);
}
start();
I’ll write this up more formally with some examples shortly, but this may help for now
If you have no idea what I am talking about, read up on requestAnimationFramein this tutorial.
<!DOCTYPE html>
<html>
<head>
<title>requestAnimationFrame Throttling Example</title>
<style>
body {
margin: 50px;
}
#myCanvas {
border: 5px solid #EEE;
}
#container p {
font-family: sans-serif;
font-size: 12pt;
}
#container div p {
display: inline-block;
}
#fpsDropdown {
font-size: 16px;
font-weight: bold;
}
</style>
</head>
<body>
<div id="container">
<canvas id="myCanvas" height="400" width="400"></canvas>
<div>
<p>Frames per second:</p>
<select id="fpsDropdown">
<option value="1">1</option>
<option value="5">5</option>
<option value="15">15</option>
<option value="30">30</option>
<option selected="selected" value="60">60</option>
<option value="120">120</option>
<option value="240">240</option>
</select>
<div>
</div>
<script>
var currentTime;
var elapsedTime;
var startTime;
// set the frame rate you want!
var fps = 60;
var interval = 1000 / fps;
var mainCanvas = document.querySelector("#myCanvas");
var mainContext = mainCanvas.getContext('2d');
var canvasWidth = mainCanvas.width;
var canvasHeight = mainCanvas.height;
var selector = document.querySelector("#fpsDropdown");
selector.addEventListener("change", updateFPS, false);
function updateFPS(e) {
var selectIndex = selector.options.selectedIndex;
fps = parseInt(selector.options[selectIndex].value);
interval = 1000 / fps;
console.log("New fps is: " + fps);
start();
}
// this array contains a reference to every circle that you will create
var circles = new Array();
//
// The Circle "constructor" is responsible for creating the circle
// objects and defining the various properties they have
//
function Circle(angle, sign, radius, rotationRadius, initialX, initialY) {
this.angle = angle;
this.sign = sign;
this.radius = radius;
this.rotationRadius = rotationRadius;
this.initialX = initialX;
this.initialY = initialY;
this.incrementer = .01 + Math.random() * .1;
}
Circle.prototype.update = function () {
this.angle += this.incrementer;
this.currentX = this.initialX + this.rotationRadius * Math.cos(this.angle);
this.currentY = this.initialY + this.rotationRadius * Math.sin(this.angle);
if (this.angle >= (Math.PI * 2)) {
this.angle = 0;
this.incrementer = .01 + Math.random() * .1;
}
// The following code is responsible for actually drawing the circle
mainContext.beginPath();
mainContext.arc(this.currentX, this.currentY, this.radius,
0, Math.PI * 2, false);
mainContext.closePath();
mainContext.fillStyle = 'rgba(0, 51, 204, .1)';
mainContext.fill();
};
//
// This function creates the circles that you end up seeing
//
function createCircles() {
// change the range of this loop to adjust the number of circles you see
for (var i = 0; i < 50; i++) {
var radius = 5 + Math.random() * 100;
var initialX = canvasWidth / 2;
var initialY = canvasHeight / 2;
var rotationRadius = 1 + Math.random() * 30;
var angle = Math.random() * 2 * Math.PI;
var signHelper = Math.floor(Math.random() * 2);
var sign;
// Randomly specify the direction the circle will be rotating
if (signHelper == 1) {
sign = -1;
} else {
sign = 1;
}
// create the Circle object
var circle = new Circle(angle,
sign,
radius,
rotationRadius,
initialX,
initialY);
circles.push(circle);
}
// call the draw function approximately 60 times a second
start();
}
createCircles();
function loop() {
currentTime = performance.now();
elapsedTime = currentTime - startTime;
if (elapsedTime > interval) {
// this seems weird, but it is done to account for
// the time it takes to display a frame
startTime = currentTime - (elapsedTime % interval);
mainContext.clearRect(0, 0, canvasWidth, canvasHeight);
mainContext.fillStyle = '#F6F6F6';
mainContext.fillRect(0, 0, canvasWidth, canvasHeight);
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
circle.update();
}
}
requestAnimationFrame(loop);
}
// Start the timer!
function start() {
startTime = performance.now();
requestAnimationFrame(loop);
}
start();
</script>
</body>
</html>