Как исправить отставание производительности в canvas html5? - PullRequest
0 голосов
/ 31 марта 2020

Я строю проект, в котором пользователь может вводить слово во входном тексте и с введенным значением canvas dr aws частицы на текст. Когда мышь наводится на частицы, отталкивающиеся назад и возвращающиеся назад (базовая анимация)

Однако производительность просто ужасна, она слишком медленная, и я искал в Интернете и нашел такие вещи, как частота кадров, соотношение экрана , getImageData, putImageData, новый Uint32Array (), побитовый оператор и т. д. c, но после нескольких часов попыток по-разному я заметил, что никакого прогресса не добился, скорее застрял глубже

Мои коды ниже, и если кто-нибудь можете сказать мне, где я должен go об исправлении, это было бы здорово.

в индексе. html

<canvas> </canvas>

<form>  
  <input class="text" type="text" value="touch me!" placeholder="type your message.."/>
  <div class="input-bottom"></div>
</form> 

в приложении. js - я не включил ни одного код для отправки формы, так как он отлично работает

let canvas = document.querySelector(".canvas")
let canvasContext2d = canvas.getContext("2d") 
let canvasWidth = canvas.width = window.innerWidth
let canvasHeight = canvas.height = window.innerHeight

let form = document.querySelector('form')
let text = form.querySelector(".text")
let textMessage = text.value 

let mouse = {x: undefined, y: undefined}

function Particle(x, y, r, accX, accY){
    this.x = randomIntFromRange(r, canvasWidth-r)
    this.y = randomIntFromRange(r, canvasHeight-r)
    this.r = r
    this.color = "black"
    this.velocity = {
      x: randomIntFromRange(-10, 10), 
      y: randomIntFromRange(-10, 10)
     }
    this.dest = {x : x, y : y}
    this.accX = 5;
    this.accY = 5;
    this.accX = accX;
    this.accY = accY;
    this.friction = randomNumDecimal(0.94, 0.98)


    this.draw = function(){    
     canvasContext2d.beginPath()
     canvasContext2d.arc(this.x, this.y, this.r, 0, Math.PI * 2)
     canvasContext2d.fillStyle = "rgb(250, 250, 247)"
     canvasContext2d.fill()
     canvasContext2d.closePath() 

     // mouse ball
     canvasContext2d.beginPath()
     canvasContext2d.arc(mouse.x, mouse.y, 50, 0, Math.PI * 2)
     canvasContext2d.fill()
     canvasContext2d.closePath()
   }

   this.update = function(){
     this.draw()

     if(this.x + this.r > canvasWidth || this.x - this.r < 0){
          this.velocity.x = -this.velocity.x
      }

    if(this.y + this.r > canvasHeight || this.y - this.r < 0){
         this.velocity.y = -this.velocity.y
      }

    this.accX = (this.dest.x - this.x) / 300;
    this.accY = (this.dest.y - this.y) / 300;

    this.velocity.x += this.accX;
    this.velocity.y += this.accY;

    this.velocity.x *= this.friction;
    this.velocity.y *= this.friction;

    this.x += this.velocity.x;
    this.y += this.velocity.y;

   if(dist(this.x, this.y, mouse.x, mouse.y) < 70){
     this.accX = (this.x - mouse.x) / 30;
     this.accY = (this.y - mouse.y) / 30;
     this.velocity.x += this.accX;
     this.velocity.y += this.accY;
    }
  }
}

let particles;
function init(){
  particles = []

  canvasContext2d.font = `bold ${canvasWidth/10}px sans-serif`;
  canvasContext2d.textAlign = "center"
  canvasContext2d.fillText(textMessage, canvasWidth/2, canvasHeight/2)

  let imgData = canvasContext2d.getImageData(0, 0, canvasWidth, canvasHeight)
  let data = imgData.data

  for(let i = 0; i < canvasWidth; i += 4){
    for(let j = 0; j < canvasHeight; j += 4){
      if(data[((canvasWidth * j + i) * 4) + 3]){
        let x = i + randomNumDecimal(0, 3)
        let y = j + randomNumDecimal(0, 3)
        let r = randomNumDecimal(1, 1.5)
        let accX = randomNumDecimal(-3, 0.2)
        let accY = randomNumDecimal(-3, 0.2)

        particles.push(new Particle(x, y, r, accX, accY))
      } 
    }
  }
} 

function animate(){
  canvasContext2d.clearRect(0, 0, canvasWidth, canvasHeight)
  for(let i = 0; i < particles.length; i++){
    particles[i].update()
  } 
  requestAnimationFrame(animate)
}

init()
animate()

1 Ответ

1 голос
/ 01 апреля 2020

JavaScript точно не для такой интенсивной обработки. За прошедшие годы производительность повысилась, но она все равно будет медленной, если вы будете использовать такое большое количество вычислений и рисование на таком большом холсте.

Создание полноэкранного холста и основание количества частиц на этом размере создает много объектов. requestAnimationFrame оптимизирует методы рисования, рисуя только при перерисовке экрана, но это примерно каждые 17 миллисекунд, поэтому, например, на моем экране, который создает 2048 экземпляров частиц из вашего кода, мы имеем, что

Каждые 17 миллисекунд:

  • 2048 частиц, умноженных не менее чем на 16 базисных c математических операций = 32 768,
  • 2048 квадратных root операций (при условии, что вы используете их для своего dist метода расстояния), которые общеизвестно дороги,
  • Подсчет длины частиц для каждой итерации по 2048 частицам (т.е. 2048 операций по проверке длины частиц)

Итак, даже не принимая во внимание, какие математические операции дороже, это выполняет

(1000/17) *2048* (16 + 1 + 1) = 2 168 470 (грубо) математических операций в секунду,

, что не требует во внимание: обработка MouseEvent, которая хранит вашу мышь x, y на ходу, очистка всего холста (который устанавливает канун увеличьте один пиксель до rgba(0,0,0,0), например, на моем экране, который составляет 1158144 пикселей, каждое из которых определяется 4 значениями данных: r, g, b, a) и прорисовкой каждого пикселя каждой из этих 2048 частиц.

Если вы настаиваете на использовании JavaScript, даже несколько приемов оптимизации не помогут вам сами. Вам придется пойти на некоторые жертвы, чтобы уменьшить объем происходящего, например:

  • уменьшить размер холста (вы можете использовать CSS transform: scale, чтобы увеличить его, если вы must),
  • уменьшить количество частиц,
  • использовать менее дорогую / менее точную операцию расстояния, такую ​​как просто проверку горизонтального расстояния и вертикального расстояния между двумя объектами,
  • использовать целое число значения вместо плавающих (рисование на холст с плавающими стоит дороже)
  • рассмотрите возможность использования fillRect вместо рисования дуг. (При таком небольшом размере это не будет иметь большого значения визуально, но теоретически вам потребуется меньше вычислений для рисования - вы можете проверить, имеет ли это большое значение),
  • даже рассмотреть возможность уменьшения как часто вы перерисовываете холст (добавляя setTimeout, чтобы обернуть requestAnimationFrame)

Я уверен, что вы, если сможете, можете не тратить время на жертвы, поэтому вы можете сделать две оптимизации теперь:

  • сохраните particles.length в переменной после того, как они созданы, чтобы вы не вычисляли частоту частиц на каждой итерации for l oop в вашей animate функция. Но миллионы вычислений минус 2048 не будут иметь большого значения.

  • устанавливают контекст fillStyle только один раз. Вы никогда не меняете этот цвет, так зачем устанавливать его на каждом розыгрыше?

  • удалить closePath() линии. Здесь они ничего не делают.

  • рисуют частицы на заэкранном «буферном» холсте и рисуют этот холст на экране только после того, как все частицы были нарисованы на нем. Это можно сделать с помощью обычного <canvas> объекта, но в зависимости от того, с каким браузером вы работаете, вы также можете посмотреть OffscreenCanvas . Пример Basi c будет выглядеть примерно так:

var numParticles;

// Inside init(), after creating all the Particle instances
numParticles = particles.length;


function animate(){
  // Note: if offscreen canvas has background color drawn, this line is unnecessary
  canvasContext2d.clearRect(0, 0, canvasWidth, canvasHeight)

  for(let i = 0; i < numParticles; i++){
    particles[i].update() // remove .draw() from .update() method
    particles[i].drawToBuffer(); // some new method drawing to buffer canvas
  }

  drawBufferToScreen(); // some new method drawing image from buffer to onscreen canvas
  requestAnimationFrame(animate)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...