I am trying to make a somewhat simple animation work on react-native. I'm using react-native-canvas
and it seems to implement the normal html canvas well enough but I get some awful flickering on a real device. I get almost no flickering on iOS emulator but the Android one is bad.
My current code already implements the following suggestions I found:
- Use
requestAnimationFrame
- Double-buffering
- Framerate handling
- Avoid using states for variables (as they re-trigger renders)
My goal is to get an animation that doesn't flicker. I don't mind if on a slower device it plays a bit slower but flickering is not a good feeling for the user.
What am I doing incorrectly?
Here is the code:
initTempCanvas = (canvas) => {
if (canvas !== null) {
canvas.width = this.window_size.height;
canvas.height = this.window_size.width + 50;
this.tempCanvas = canvas;
this.tempCtx = canvas.getContext('2d');
canvas.hidden = true;
}
}
initCanvas = (canvas) => {
if (canvas !== null) {
var fps = 25;
this.interval = 1000 / fps;
this.speed = 30;
// These are switched because of landscape mode
canvas.width = this.window_size.height;
canvas.height = this.window_size.width + 50;
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
requestAnimationFrame(this.drawLoop);
}
}
drawLoop = (now) => {
if (this.tempCtx !== undefined) {
if (!this.then) {
this.then = now;
}
this.delta = now - this.then;
if (this.delta > this.interval) {
this.ctx.save();
this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height)
this.drawCircle(this.tempCtx, (this.canvasTimer * 100), this.tempCanvas.height / 2 + (-125 * Math.sin(this.canvasTimer * (Math.PI / 2) - Math.PI * 0.5)), 30, 'purple', 'purple', 2);
this.ctx.drawImage(this.tempCanvas, 0, 0);
this.ctx.restore();
this.deltaTime = this.delta / 100000 * this.speed;
this.canvasTimer += this.deltaTime;
}
this.then = now - (this.delta % this.interval);
}
requestAnimationFrame(this.drawLoop);
}
drawCircle = (ctx, x, y, radius, fill, stroke, strokeWidth) => {
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
if (fill) {
ctx.fillStyle = fill;
ctx.fill();
}
if (stroke) {
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = stroke;
ctx.stroke();
}
}
render() {
return (
<View>
{this.tempCanvas == undefined && <Canvas ref={this.initCanvas} />}
<Canvas style={"display: none"} ref={this.initTempCanvas} />
</View>
)
}