рендеринг огромного количества элементов на html5 холсте - PullRequest
2 голосов
/ 06 мая 2020

Предположим, у вас есть 2D-холст размером 500x500, и вы хотите анимировать на нем 100000 элементов, например, вы хотите создать шумовые эффекты. рассмотрите приведенный ниже код:

    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);
    }

он едва дает вам 2 или 3 кадра в секунду, и это просто недопустимо, мне было интересно, есть ли уловка с такими анимациями или чем-то еще, чтобы плавно отображать огромное количество элементов !

1 Ответ

2 голосов
/ 06 мая 2020

Можно играть по памяти и после этого рисовать на невидимом холсте. И когда вы будете готовы, скопируйте все байты на видимый холст. И я вижу, вы используете много случайного. Это медленная инструкция. Попробуйте создать случайную таблицу и реализовать свою собственную случайную функцию

Вот версия со скоростью 12-15 кадров в секунду, но я думаю, вы можете достичь лучшей производительности, манипулируя пикселями. Итак, этот код основан на вашем решении, но я не могу увеличить fps, потому что слишком много вызовов функций, манипулирования объектами и подобной пахлавы. (код ниже достигает более 100 кадров в секунду)

<!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>

А вот решение для манипулирования пикселями с гораздо большей производительностью (более 100 кадров в секунду, 220–245 кадров в секунду на моем компьютере):

<!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>

Пояснение: для шума вам не нужна функция / объект для каждого цветного пикселя. Доверьтесь статистике и случайности. В моем примере 10% пикселей окрашены, но мы не знаем, сколько пикселей перед рендерингом. Но это не важно. Издалека это просто так идеально. И самое главное: он может набирать больше кадров в секунду.

Общий совет:

  • Какой код повторяется много раз, организуйте из него что сможете
  • Рисуйте только на холсте, когда закончите рисовать в памяти
  • Измерьте то, что идет медленно, и оптимизируйте его, и только это
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...