如何修复 canvas html5 中的性能滞后问题?
How to fix performance lag in canvas html5?
我正在构建一个项目,用户可以在其中输入文字并使用输入值,canvas 将粒子绘制到文字上。当鼠标悬停在被推回去的粒子上(核心动画)
然而,性能太糟糕了,太慢了,我一直在网上查找并找到帧速率、显示比率、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/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()
首先,您可以考虑减少全屏像素数的总工作量,例如:
- 减小 canvas 大小(如有必要,您可以考虑使用 CSS
transform: scale
将其放大),
- 减少粒子数量,
- 使用较少的 expensive/less 精确距离操作,例如检查两个对象之间的水平距离和垂直距离,
- 使用整数值而不是浮点数(使用浮点数绘制到 canvas 更昂贵)
- 考虑使用
fillRect
而不是绘制圆弧。 (在这么小的尺寸下,它在视觉上不会产生太大的差异,但通常绘制它们的成本较低 - 你可能想测试它是否会产生很大的差异),
- 甚至考虑减少重绘 canvas 的频率(添加
setTimeout
来包裹 requestAnimationFrame
并增加帧之间的延迟(requestAnimationFrame 通常约为 17 毫秒))
以及代码中的一些小优化:
在创建变量后将 particles.length
存储在变量中,这样您就不会在 animate
中的每个 for
循环迭代中计算 particles.length ] 功能。但是数百万次计算减去这个 2048 不会有太大的不同。
只设置上下文fillStyle
一次。您永远不会更改此颜色,所以为什么要在每次抽奖时都设置它?
删除 closePath()
行。他们在这里什么都不做。
将粒子绘制到屏幕外的“缓冲区”canvas,只有在所有粒子都被绘制到它之后才将 canvas 绘制到屏幕上的缓冲区。这可以使用普通的 <canvas>
对象来完成,但根据您使用的浏览器,您也可以查看 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)
}
我正在构建一个项目,用户可以在其中输入文字并使用输入值,canvas 将粒子绘制到文字上。当鼠标悬停在被推回去的粒子上(核心动画)
然而,性能太糟糕了,太慢了,我一直在网上查找并找到帧速率、显示比率、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/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()
首先,您可以考虑减少全屏像素数的总工作量,例如:
- 减小 canvas 大小(如有必要,您可以考虑使用 CSS
transform: scale
将其放大), - 减少粒子数量,
- 使用较少的 expensive/less 精确距离操作,例如检查两个对象之间的水平距离和垂直距离,
- 使用整数值而不是浮点数(使用浮点数绘制到 canvas 更昂贵)
- 考虑使用
fillRect
而不是绘制圆弧。 (在这么小的尺寸下,它在视觉上不会产生太大的差异,但通常绘制它们的成本较低 - 你可能想测试它是否会产生很大的差异), - 甚至考虑减少重绘 canvas 的频率(添加
setTimeout
来包裹requestAnimationFrame
并增加帧之间的延迟(requestAnimationFrame 通常约为 17 毫秒))
以及代码中的一些小优化:
在创建变量后将
particles.length
存储在变量中,这样您就不会在animate
中的每个for
循环迭代中计算 particles.length ] 功能。但是数百万次计算减去这个 2048 不会有太大的不同。只设置上下文
fillStyle
一次。您永远不会更改此颜色,所以为什么要在每次抽奖时都设置它?删除
closePath()
行。他们在这里什么都不做。将粒子绘制到屏幕外的“缓冲区”canvas,只有在所有粒子都被绘制到它之后才将 canvas 绘制到屏幕上的缓冲区。这可以使用普通的
<canvas>
对象来完成,但根据您使用的浏览器,您也可以查看 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)
}