javascript - 优化 Canvas 画圆

标签 javascript reactjs html5-canvas

我是 HTML5 Canvas 的新手,希望让几个圆圈在随机方向上移动,以便在我的网站上产生奇特的效果。

我注意到,当这些圆圈移动时,CPU 使用率非常高。当只有几个圆圈在移动时,通常是可以的,但是当大约有 5 个或更多时,它就开始出现问题了。

这是在 Safari 中使用 5 个圆圈对其进行分析几秒钟的屏幕截图。

Profile Results

这是到目前为止我的 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/

相关文章:

javascript - CSS width=auto 受 parent.wadth 限制

javascript - 鼠标操作时函数重复

javascript - html5如何用倒置的背景颜色进行描边?

javascript - Express POST - 返回的 JSON 被 JQuery 视为无效

javascript - 如何根据显示的记录更改图像和 JS?

javascript - 如何从异步调用中填充 ng-table 上的选择过滤器

javascript - HTML5 canvas 中的简单加载动画

reactjs - 为什么 react-sortable-hoc 基本示例无法使用 typescript 进行编译?

javascript - 类型错误 : Cannot read property 'setState' of null

javascript - ReactJS - 有没有办法通过在 &lt;input/> 中按下 'Enter' 键来触发方法?