如何修复画布 html5 中的性能滞后?

Posted

技术标签:

【中文标题】如何修复画布 html5 中的性能滞后?【英文标题】:How to fix performance lag in canvas html5? 【发布时间】:2020-07-12 11:43:31 【问题描述】:

我正在构建一个项目,用户可以在输入文本中键入单词,并使用输入值,画布将粒子绘制到文本上。当鼠标悬停在被推回的粒子上并返回时(核心动画)

但是,性能很糟糕,太慢了,我一直在网上查找,发现帧速率、显示比例、getImageData、putImageData、new Uint32Array()、按位运算符等,但经过数小时的尝试不同我注意到我没有取得任何进展而是陷入更深的事情

我的代码如下,如果有人能告诉我应该去哪里修复它会很棒。

在 index.html

<canvas> </canvas>

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

在 app.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/10px 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】:

首先,您可以考虑减少全屏像素数的总工作量,例如:

减小画布大小(如果必须,可以考虑使用 CSS transform: scale 将其放大), 减少粒子数, 使用成本较低/精度较低的距离操作,例如仅检查两个对象之间的水平距离和垂直距离, 使用整数值而不是浮点数(使用浮点数绘制到画布更昂贵) 考虑使用fillRect 而不是绘制圆弧。 (在这么小的尺寸下,它在视觉上不会有太大的不同,但这些通常绘制起来更便宜 - 您可能需要测试它是否有很大的不同), 甚至可以考虑减少重绘画布的频率(添加 setTimeout 以包裹 requestAnimationFrame 并增加帧之间的延迟(requestAnimationFrame 通常约为 17 毫秒))

还有一些代码中的小优化:

在创建 particles.length 后将它们存储在变量中,这样您就不会在 animate 函数中的每个 for 循环迭代中计算particles.length。但数百万次计算减去这个 2048 并不会产生太大影响。

只设置一次上下文fillStyle。你永远不会改变这个颜色,那为什么要在每次绘制时都设置它呢?

删除closePath() 行。他们在这里什么都不做。

将粒子绘制到屏幕外的“缓冲区”画布上,并且只有在所有粒子都被绘制到它之后才将该画布绘制到屏幕上。这可以使用普通的&lt;canvas&gt; 对象来完成,但根据您使用的浏览器,您也可以查看OffscreenCanvas。基本示例如下所示:

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)

【讨论】:

嘿,感谢您的帮助?如果我可以进一步了解您的上述建议之一,尽管它会导致低分辨率,但可以立即减小画布的大小!所以从我在stack-overflow上找到的这个项目jsfiddle.net/L8vtLqhu/1(他也在询问性能问题)他修复了代码,并且在链接中的项目中它非常快,但他将canvas.width和canvas.height设置为加倍innerWidth 和 innerHeight 的大小(比率 = 2)你能解释一下这是如何工作的吗? 快速检查分辨率:确保画布的宽度属性与 style.width 匹配,并且高度相似(否则你会得到一些奇怪的缩放/拉伸)。 jsfiddle 链接对我来说在没有缩放的情况下工作得更快(即,当我将比率更改为 1 时),因为将宽度和高度加倍,他实际上使整体像素图像数据翻了两番。他似乎将所有内容加倍,然后使用 CSS 缩小输出。结果应该看起来不错,但性能会受到影响。但是你可以考虑尝试这个(在相反的方向),比如将画布 CSS 设置为 transform: scale(2); 他所做的最大优化改进本质上是在使用屏幕外画布策略,但使用 Unit32Array 缓冲区中的原始数据而不是绘制到另一个画布。屏幕渲染成本很高,所以他先将所有像素信息存储在一个对象中,然后使用developer.mozilla.org/en-US/docs/Web/API/… 一次将其绘制到屏幕上(而不是一次一个粒子)。 嘿先生,您夸大了 javascript 无法进行这些大型计算。处理器在做这些事情,它能够在几微秒内做更多的事情......你不应该指出计算量。相反,请给出一些建议来优化他/她的代码。

以上是关于如何修复画布 html5 中的性能滞后?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用javascript修复HTML画布对象中的扭曲/扭曲和剪切图像?

画布中的 HTML5 图像适合问题

如何修复此错误在多个渲染()操作期间无法使用相同的画布

如何在 Mapbox GL 中修复画布大小?

尝试修复 AVAudioPlayer 在其初始使用时的滞后

如何修复画布中重叠的VBox儿童?