Reputation: 92
Assume you have a 500x500 2D canvas and you want to animate 100000 of elements in it, for example you want to create noise effects. consider code bellow :
const canvas = document.getElementById("plane");
let animatelist = [];
animate = function() {
animatelist.forEach((e) => {
e.render();
});
setTimeout(animate, 1000 / 30);
}
animate();
let point = function(plane, x, y, size) {
animatelist.push(this);
this.plane = plane;
this.x = x;
this.y = y;
this.size = size;
this.render = () => {
const context = this.plane.getContext("2d");
this.x = Math.random() * 500;
this.y = Math.random() * 500;
context.fillStyle = "#000";
context.fillRect(this.x, this.y, this.size, this.size);
}
}
for (let i = 0;i < 100000;i++) {
new point(canvas, Math.random() * 500, Math.random() * 500, 0.3);
}
it barely gives you 2 or 3 fps and it is just unacceptable, i was wondering if there is a trick a about these kinda of animations or something to render massive amounts of elements smoothly!
Upvotes: 2
Views: 1575
Reputation: 1542
You can play in memory and after that draw on an invisuble canvas. And when you are ready, copy all of bytes into visible canvas. And i see, you use a lot of random. This is slow instruction. Try to make a random table and implement your own random function
Here is an 12-15 fps version but I think you can reach better performance by pixel manipulating. So this code based on your solution, but I cannot increase fps because too many function calls, object manipulating and similar baklava. (a code below reach over 100 fps)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sarkiroka</title>
</head>
<body>
<canvas id="plane" width="500" height="500"></canvas>
<script>
// variable and function for speedup
const randomTable = [];
const randomTableLength = 1000007;
const fpsMinimum = 1000 / 30;
for (let i = 0; i < randomTableLength; i++) {
randomTable.push(Math.random() * 500);
}
let randomSeed = 0;
function getNextRandom() {
if (++randomSeed >= randomTableLength) {
randomSeed = 0;
}
return randomTable[randomSeed];
}
// html, dom speedup
const canvas = document.getElementById("plane");
const context = canvas.getContext("2d");
const drawCanvas = document.createElement('canvas');
drawCanvas.setAttribute('width', canvas.getAttribute('width'));
drawCanvas.setAttribute('height', canvas.getAttribute('height'));
const drawContext = drawCanvas.getContext('2d');
drawContext.fillStyle = "#000";
let animatelist = [];
let point = function (x, y, size) {
animatelist.push(this);
this.x = x;
this.y = y;
this.size = size;
this.render = () => {
this.x = getNextRandom();
this.y = getNextRandom();
drawContext.fillRect(this.x, this.y, this.size, this.size);
}
}
for (let i = 0; i < 100000; i++) {
new point(getNextRandom(), getNextRandom(), 0.3);
}
//the animation
let lastBreath = Date.now();
const animateListLength = animatelist.length;
let framesDrawed = 0;
let copied = false;
const maximumCallstackSize = 100;
function continouslyAnimation(deep) {
if (copied) {
drawContext.clearRect(0, 0, 500, 500);
for (let i = 0; i < animateListLength; i++) {
animatelist[i].render();
}
copied = false;
}
framesDrawed++;
let now = Date.now();
if (lastBreath + 15 > now && deep < maximumCallstackSize) {
continouslyAnimation(deep + 1);
} else { // to no hangs browser
lastBreath = now;
setTimeout(continouslyAnimation, 1, 1);
}
}
setInterval(() => {
console.log(framesDrawed);
framesDrawed = 0;
}, 1000);
continouslyAnimation(0);
function copyDrawToVisible() {
context.putImageData(drawContext.getImageData(0, 0, 499, 499), 0, 0);
copied = true;
}
setInterval(copyDrawToVisible, fpsMinimum);
</script>
</body>
</html>
And here is a pixel manipulation solution, with much better performance (over 100 fps, 220-245 fps in my computer):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sarkiroka</title>
</head>
<body>
<canvas id="plane" width="500" height="500"></canvas>
<script>
// variable and function for speedup
const randomTable = [];
const randomTableLength = 1000007;
for (let i = 0; i < randomTableLength; i++) {
randomTable.push(Math.random());
}
let randomSeed = 0;
function getNextRandom() {
if (++randomSeed >= randomTableLength) {
randomSeed = Math.round(Math.random() * 1000);
}
return randomTable[randomSeed];
}
// html, dom speedup
const canvas = document.getElementById("plane");
const context = canvas.getContext("2d");
let framesDrawed = 0;
function drawNoise() {
context.clearRect(0, 0, 500, 500);
let imageData = context.createImageData(499, 499);
let data = imageData.data;
for (let i = 0, length = data.length; i < length; i += 4) {
if (0.1 > getNextRandom()) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
data[i + 3] = 255;
}
}
context.putImageData(imageData, 0, 0);
framesDrawed++;
}
setInterval(drawNoise, 0);
setInterval(() => {
console.log('fps', framesDrawed);
framesDrawed = 0;
}, 1000)
</script>
</body>
</html>
Explanataion: for noise, you don't need a function / object for every colored pixel. Trust to statistics and the random. In my example 10% of pixels are colored but we don't know how many pixel before render. But this is not important. From afar it is just like that perfect. And the most important thing: it can reach more fps.
General advice:
Upvotes: 3