This example benchmarks the time taken to animate in Canvas with each frame consisting of a 5000 segment line.
Last active
April 6, 2023 21:03
-
-
Save stepheneb/1299659 to your computer and use it in GitHub Desktop.
Canvas Animate Path Benchmark
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Canvas Animate Path Benchmark</title> | |
<style type="text/css"> | |
body { margin: 0px 10px; } | |
h1 { font-size: 1.3em; line-height: 1.0em; } | |
table { font-size: 0.8em; margin: 0px 10px; padding: 2px } | |
th { text-align: center; font-weight: bold; padding: 0px 10px 0px 10px; } | |
td { text-align: center; font-weight: normal; padding: 0px 10px 0px 10px; } | |
ul.hlist { display: inline-block; list-style-type: none; margin: 0px; padding-left: 15px; } | |
ul.hlist li { display: inline-table; vertical-align: top; list-style-type: none } | |
#canvas { width: 400px; height: 400px; padding:0px; | |
background-color: #eeeeee; color:gray; border: solid 1px #cccccc } | |
</style> | |
<script type="text/javascript"> | |
window.requestAnimFrame = (function() { | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { | |
return window.setTimeout(callback, 1000/60); | |
}; | |
})(); | |
window.cancelRequestAnimFrame = (function() { | |
return window.cancelCancelRequestAnimationFrame || | |
window.webkitCancelRequestAnimationFrame || | |
window.mozCancelRequestAnimationFrame || | |
window.oCancelRequestAnimationFrame || | |
window.msCancelRequestAnimationFrame || | |
window.clearTimeout; | |
})(); | |
</script> | |
</head> | |
<body> | |
<h1>Canvas Animate Path Benchmark</h1> | |
<p>Measure performance of canvas line drawing speed by repeatedly re-drawing a 5000 element line.</p> | |
<ul class="hlist"> | |
<li><canvas id="canvas"></canvas></li> | |
<li> | |
<form id="animate-controller"> | |
<fieldset> | |
<legend>Step</legend> | |
<label><input type="radio" name="step" value="stop" checked> Stop</input></label> | |
<label><input type="radio" name="step" value="step"> Step</input></label> | |
<label><input type="radio" name="step" value="go"> Go</input></label> | |
<label><input type="radio" name="step" value="reset"> Reset</input></label> | |
</fieldset> | |
</form> | |
<p>frame: <span id="frame"></span></p> | |
<p>framerate: <span id="framerate"></span></p> | |
<p><i>Animation will stop automatically after 250 frames.</i></p> | |
</li> | |
</ul> | |
<p id="user-agent"></p> | |
<script type="text/javascript"> | |
window.onload=function() { | |
var user_agent = document.getElementById("user-agent"); | |
user_agent.innerHTML = navigator.userAgent; | |
var canvas = document.getElementById("canvas"); | |
canvas.width = canvas.clientWidth; | |
canvas.height = canvas.clientHeight; | |
var animateRequest; | |
var animate_controller = document.getElementById("animate-controller"); | |
var animate_controller_inputs = animate_controller.getElementsByTagName("input") | |
var frame = document.getElementById("frame"); | |
var framerate = document.getElementById("framerate"); | |
var animating; | |
var start_time, step_time; | |
var loop_start, loop_time, loop_elapsed; | |
var half_width = canvas.width / 2; | |
var half_height = canvas.height / 2; | |
var xpos = half_width; | |
var ypos = half_height; | |
var pos; | |
var path = []; | |
var ctx = canvas.getContext('2d'); | |
ctx.globalCompositeOperation = "destination-atop"; | |
ctx.lineWidth = 1; | |
ctx.strokeStyle = "rgba(255,65,0, 1.0)"; | |
var i, j; | |
var factor = 15; | |
var factor_div_2 = factor / 2; | |
var correction = 0.002; | |
var transform_factor = 5; | |
var transform_factor_div_2 = transform_factor / 2; | |
var transform_correction = 0.00035; | |
var count, max_count; | |
var run_mode; | |
function init() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
count = 0; | |
max_count = 250; | |
xpos = half_width; | |
ypos = half_height; | |
path[0] = [xpos, ypos]; | |
for (i=1; i < 5000; i++) { | |
xpos += (factor * Math.random() - factor_div_2) + (half_width - xpos) * correction; | |
ypos += (factor * Math.random() - factor_div_2) + (half_height - ypos) * correction; | |
path[i] = [xpos, ypos]; | |
}; | |
}; | |
function drawPath() { | |
ctx.beginPath(); | |
pos = path[0]; | |
ctx.moveTo(pos[0], pos[1]); | |
for (var i=1; i < 5000; i++) { | |
pos = path[i]; | |
ctx.lineTo(pos[0], pos[1]); | |
}; | |
ctx.closePath(); | |
ctx.stroke(); | |
}; | |
function transformPath() { | |
for (i=1; i < 5000; i++) { | |
path[i][0] += (transform_factor * Math.random() - transform_factor_div_2) + (half_width - xpos) * -correction; | |
path[i][1] += (transform_factor * Math.random() - transform_factor_div_2) + (half_height - ypos) * -correction; | |
}; | |
}; | |
function animateController() { | |
for(var i = 0; i < this.elements.length; i++) | |
if (this.elements[i].checked) run_mode = this.elements[i].value; | |
switch(run_mode) { | |
case "stop": | |
animateStop(); | |
break; | |
case "step": | |
if (animateRequest) cancelRequestAnimFrame(animateRequest); | |
animating = false; | |
if (count < max_count) runAnimateStep(); | |
animate_controller_inputs[0].checked = true; | |
break; | |
case "go": | |
start_time = +new Date(); | |
animating = true; | |
if (count >= max_count) { | |
init(); | |
drawPath(); | |
animateRequest = requestAnimFrame(animateLoop, canvas); | |
} else { | |
animateRequest = requestAnimFrame(animateLoop, canvas); | |
}; | |
break; | |
case "reset": | |
animateStop(); | |
init(); | |
drawPath(); | |
break; | |
} | |
}; | |
function animateStop() { | |
if (animateRequest) cancelRequestAnimFrame(animateRequest); | |
animating = false; | |
animate_controller_inputs[0].checked = true; | |
}; | |
function runAnimateStep() { | |
count++; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
transformPath(); | |
drawPath(); | |
frame.innerHTML = count; | |
step_time = +new Date(); | |
framerate.innerHTML = ~~(10000 / ((step_time - start_time) / count)) / 10; | |
// see: http://jsperf.com/rounding-numbers-down | |
// I'm also rendering with one digit to the right of the decimal point | |
}; | |
function animateLoop(){ | |
if (count < max_count && animating) { | |
loop_start = +new Date(); | |
animateRequest = requestAnimFrame(animateLoop, canvas); | |
runAnimateStep(); | |
loop_time = +new Date() | |
loop_elapsed = loop_time - loop_start; | |
while (loop_elapsed < 15) { | |
runAnimateStep(); | |
loop_time = +new Date() | |
loop_elapsed = loop_time - loop_start; | |
} | |
} else { | |
animateStop() | |
} | |
}; | |
animate_controller.onchange = animateController; | |
init(); | |
drawPath(); | |
}; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment