我是 HTML5 Canvas 的新手,希望让几个圆圈在随机方向上移动,以便在我的网站上产生奇特的效果。
我注意到,当这些圆圈移动时,CPU 使用率非常高。当只有几个圆圈在移动时,通常是可以的,但是当大约有 5 个或更多时,它就开始出现问题了。
这是在 Safari 中使用 5 个圆圈对其进行分析几秒钟的屏幕截图。
这是到目前为止我的 Circle 组件的代码:
export default function Circle({ color = null }) {
useEffect(() => {
if (!color) return
let requestId = null
let canvas = ref.current
let context = canvas.getContext("2d")
let ratio = getPixelRatio(context)
let canvasWidth = getComputedStyle(canvas).getPropertyValue("width").slice(0, -2)
let canvasHeight = getComputedStyle(canvas).getPropertyValue("height").slice(0, -2)
canvas.width = canvasWidth * ratio
canvas.height = canvasHeight * ratio
canvas.style.width = "100%"
canvas.style.height = "100%"
let y = random(0, canvas.height)
let x = random(0, canvas.width)
const height = random(100, canvas.height * 0.6)
let directionX = random(0, 1) === 0 ? "left" : "right"
let directionY = random(0, 1) === 0 ? "up" : "down"
const speedX = 0.1
const speedY = 0.1
context.fillStyle = color
const render = () => {
//draw circle
context.clearRect(0, 0, canvas.width, canvas.height)
context.beginPath()
context.arc(x, y, height, 0, 2 * Math.PI)
//prevent circle from going outside of boundary
if (x < 0) directionX = "right"
if (x > canvas.width) directionX = "left"
if (y < 0) directionY = "down"
if (y > canvas.height) directionY = "up"
//move circle
if (directionX === "left") x -= speedX
else x += speedX
if (directionY === "up") y -= speedY
else y += speedY
//apply color
context.fill()
//animate
requestId = requestAnimationFrame(render)
}
render()
return () => {
cancelAnimationFrame(requestId)
}
}, [color])
let ref = useRef()
return <canvas ref={ref} />
}
有没有更高效的方式来使用 Canvas 绘制和移动圆圈?
当它们不动时,CPU 使用率从大约 3% 开始,然后下降到不到 1%,当我从 DOM 中删除圆圈时,CPU 使用率始终低于 1%。
我知道用 CSS 做这些类型的动画通常会更好(因为我相信它使用 GPU 而不是 CPU),但我不知道如何使用 transition CSS 属性让它工作。我只能让规模转换工作。
只有当屏幕上有很多圆圈在移动时,我的奇特效果才会看起来“酷”,因此寻找一种更高效的方式来绘制和移动圆圈。
这是一个用于演示的沙箱:https://codesandbox.io/s/async-meadow-vx822 (在 chrome 或 safari 中查看以获得最佳效果)
最佳答案
这是一种稍微不同的方法,将圆形和背景组合在一起,只有一个 Canvas 元素来改善渲染的 dom。
该组件使用与您的随机化逻辑相同的颜色和大小,但将所有初始值存储在 circles
中。渲染任何东西之前的数组。 render
函数将背景颜色和所有圆圈一起渲染,并计算它们在每个周期中的移动。
export default function Circles() {
useEffect(() => {
const colorList = {
1: ["#247ba0", "#70c1b3", "#b2dbbf", "#f3ffbd", "#ff1654"],
2: ["#05668d", "#028090", "#00a896", "#02c39a", "#f0f3bd"]
};
const colors = colorList[random(1, Object.keys(colorList).length)];
const primary = colors[random(0, colors.length - 1)];
const circles = [];
let requestId = null;
let canvas = ref.current;
let context = canvas.getContext("2d");
let ratio = getPixelRatio(context);
let canvasWidth = getComputedStyle(canvas)
.getPropertyValue("width")
.slice(0, -2);
let canvasHeight = getComputedStyle(canvas)
.getPropertyValue("height")
.slice(0, -2);
canvas.width = canvasWidth * ratio;
canvas.height = canvasHeight * ratio;
canvas.style.width = "100%";
canvas.style.height = "100%";
[...colors, ...colors].forEach(color => {
let y = random(0, canvas.height);
let x = random(0, canvas.width);
const height = random(100, canvas.height * 0.6);
let directionX = random(0, 1) === 0 ? "left" : "right";
let directionY = random(0, 1) === 0 ? "up" : "down";
circles.push({
color: color,
y: y,
x: x,
height: height,
directionX: directionX,
directionY: directionY
});
});
const render = () => {
context.fillStyle = primary;
context.fillRect(0, 0, canvas.width, canvas.height);
circles.forEach(c => {
const speedX = 0.1;
const speedY = 0.1;
context.fillStyle = c.color;
context.beginPath();
context.arc(c.x, c.y, c.height, 0, 2 * Math.PI);
if (c.x < 0) c.directionX = "right";
if (c.x > canvas.width) c.directionX = "left";
if (c.y < 0) c.directionY = "down";
if (c.y > canvas.height) c.directionY = "up";
if (c.directionX === "left") c.x -= speedX;
else c.x += speedX;
if (c.directionY === "up") c.y -= speedY;
else c.y += speedY;
context.fill();
context.closePath();
});
requestId = requestAnimationFrame(render);
};
render();
return () => {
cancelAnimationFrame(requestId);
};
});
let ref = useRef();
return <canvas ref={ref} />;
}
您可以在应用程序组件中使用这个组件简单地替换所有圆形元素和背景样式。
export default function App() {
return (
<>
<div className="absolute inset-0 overflow-hidden">
<Circles />
</div>
<div className="backdrop-filter-blur-90 absolute inset-0 bg-gray-900-opacity-20" />
</>
);
}
关于javascript - 优化 Canvas 画圆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61999508/