Ignore my drivel. I think I misinterpreted motion blur as flickering. If I do the same in pure JavaScript, I get the same visual effects. Snap works fine.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Bounce</title>
<style>
body { background: silver; }
canvas { background: white; }
span.sign { display: inline-block; white-space: pre; width: 1ch; text-align: center; }
span.number { display: inline-block; white-space: pre; }
label { margin-right: 1ex; }
label:not(:nth-of-type(1)) { margin-left: 3em; }
</style>
</head>
<body>
<canvas width="1200" height="900"></canvas>
<div>
<label>Frame duration:</label><span id="duration"></span> ms
<label>Time used:</label><span id="timeused"></span>
<label>Acceleration:</label><span id="acceleration"></span>
<label>Step δx:</label><span id="stepx"></span>
<label>Step δy:</label><span id="stepy"></span>
</div>
</body>
<script>
class DocFloat {
#value;
constructor (parent, value, digits) {
this.sign = document.createElement('span');
this.sign.setAttribute('class', 'sign');
this.number = document.createElement('span');
this.number.setAttribute('class', 'number');
this.digits = Number(digits);
this.value = Number(value);
parent.appendChild(this.sign);
parent.appendChild(this.number);
}
get value() { return this.#value; }
set value(v) {
this.#value = v;
this.sign.innerHTML = v < 0 ? '-' : ' ';
this.number.innerHTML = Math.abs(v).toFixed(this.digits);
}
}
class Bounce {
constructor() {
this.acceleration = new DocFloat(document.getElementById('acceleration'), 20, 2);
this.duration = new DocFloat(document.getElementById('duration'), 0, 1);
this.timeused = new DocFloat(document.getElementById('timeused'), 0, 1);
this.view_canvas = document.querySelector("canvas");
this.view_context = this.view_canvas.getContext("2d");
this.draw_canvas = document.createElement('canvas');
this.draw_canvas.width = this.view_canvas.width;
this.draw_canvas.height = this.view_canvas.height;
this.draw_context = this.draw_canvas.getContext("2d");
this.items = [];
this.arid = null;
let last_time = 0;
this.animation = () => {
const frame_time = performance.now();
this.duration.value = frame_time - last_time;
last_time = frame_time;
this.animate();
this.draw_context.fillStyle = "rgb(255, 255, 255)";
this.draw_context.fillRect(
0, 0, this.draw_canvas.width, this.draw_canvas.height);
for (const item of this.items) {
item.move(this.draw_canvas)
item.draw(this.draw_context);
}
this.view_context.drawImage(this.draw_canvas, 0, 0);
this.timeused.value = performance.now() - frame_time;
};
this.view_canvas.addEventListener('click', ev => {
if (this.arid)
this.stop();
else
this.animate();
});
this.view_canvas.addEventListener('wheel', ev => {
ev.preventDefault();
switch (true) {
case ev.deltaY < 0:
this.acceleration.value = this.acceleration.value * Math.SQRT2;
break;
case ev.deltaY > 0:
this.acceleration.value = this.acceleration.value / Math.SQRT2;
break;
}
});
}
animate() {
this.arid = requestAnimationFrame(this.animation);
}
stop() {
cancelAnimationFrame(this.arid);
this.arid = null;
}
}
class Ball {
constructor(game) {
this.game = game;
this.x = 100;
this.y = 100;
this.r = 20;
this.twopi = 2 * Math.PI;
this.dx = Math.SQRT1_2;
this.dy = Math.SQRT1_2;
this.stepx = new DocFloat(document.getElementById('stepx'), 0, 1);
this.stepy = new DocFloat(document.getElementById('stepy'), 0, 1);
game.items.push(this);
}
draw(context) {
context.beginPath();
context.fillStyle = "rgb(255 0 0)";
context.arc(this.x, this.y, this.r, 0, this.twopi);
context.fill();
}
move(canvas) {
this.stepx.value = this.dx * this.game.acceleration.value;
this.x += this.stepx.value;
if (this.x > canvas.width) {
this.x = canvas.width - (this.x - canvas.width);
this.dx = - this.dx;
this.stepx.value = - this.stepx.value;
}
if (this.x < 0) {
this.x = - this.x
this.dx = - this.dx;
this.stepx.value = - this.stepx.value;
}
this.stepy.value = this.dy * this.game.acceleration.value;
this.y += this.stepy.value;
if (this.y > canvas.height) {
this.y = canvas.height - (this.y - canvas.height);
this.dy = - this.dy;
this.stepy.value = - this.stepy.value;
}
if (this.y < 0) {
this.y = - this.y
this.dy = - this.dy;
this.stepy.value = - this.stepy.value;
}
}
}
window.game = new Bounce;
window.ball = new Ball(window.game);
window.game.animate();
</script>
</html>