css 优化JavaScript动画中的性能:使用画布和像素的星落?

tjrkku2a  于 2024-01-09  发布在  Java
关注(0)|答案(2)|浏览(169)

我有一个JavaScript动画,它使用HTML画布和“像素”操作来模拟一个星落效果。当动画工作时,我面临着效率和速度问题,我正在寻找优化代码的方法。动画涉及创建从屏幕顶部落下的星星,并在它们移动时留下像素的轨迹。问题是,像素是divs,它达到了数千。
当前的JS代码:

const content = document.getElementById("bkg-content");
const background = document.getElementById("bkg");
const ctx = content.getContext("2d", { willReadFrequently: true });

let stars = [];
const maxStars = 20; // Maximum number of stars

let last_x = 0;
let next_spawn = 0;

function getX(size) {
    let random = Math.random() * (window.innerWidth - size);
    return last_x - size > random || random > last_x + size ? random : getX(size);
}

function createStar() {
    let transparency = Math.random();
    let size = transparency * 200 + 100;
    let x = getX(size)
    let fallingSpeed = Math.random() * 30 + 20;
    next_spawn = size / fallingSpeed * 500;

    last_x = x;
    return {
        x,
        y: 0,
        size,
        transparency,
        rotationAngle: 0,
        rotationSpeed: Math.random() * 0.3 + 0.05,
        rotationDirection: Math.random() < 0.5 ? 1 : -1,
        fallingSpeed,
    };
}

function spawnStars(count) {
    for (let i = 0; i < count; i++) {
        stars.push(createStar());
    }

    setTimeout(() => {
        spawnStars(1)
    }, next_spawn)

    console.log(stars.length);
}

function draw() {
    ctx.clearRect(0, 0, content.width, content.height);

    for (let i = 0; i < stars.length; i++) {
        let star = stars[i];
        star.rotationAngle += star.rotationSpeed * star.rotationDirection;
        star.y += star.fallingSpeed;

        if (star.y > window.innerHeight + star.size / 2) {
            // Remove stars that have completely passed the bottom of the screen
            stars.splice(i, 1);
            i--; // Adjust the loop variable since the array length has changed
        } else {
            ctx.save();
            ctx.translate(star.x + star.size / 2, star.y);
            ctx.rotate(star.rotationAngle);

            // Adjust transparency based on star size
            ctx.globalAlpha = star.transparency;
            console.log(ctx.globalAlpha)

            ctx.drawImage(starTXT, -star.size / 2, -star.size / 2, star.size, star.size);

            // Reset global alpha for subsequent drawings
            ctx.globalAlpha = 1;
            ctx.restore();
        }
    }
}

function resizeCanvas() {
    content.width = window.innerWidth;
    content.height = window.innerHeight;

    spawnStars(Math.min(maxStars, 1)); // Initially spawn one star

    setInterval(() => {
        createPixels()
        draw()
    }, 500);
}

function createPixels() {
    const pixel = { size: 9, gap: 3 };
    const numX = Math.floor(window.innerWidth / (pixel.size + pixel.gap));
    const numY = Math.floor(window.innerHeight / (pixel.size + pixel.gap));

    background.style.gridTemplateColumns = `repeat(${numX}, ${pixel.size}px)`;
    background.innerHTML = ''; // clear existing pixels

    for (let x = 0; x < numX; x++) {
        for (let y = 0; y < numY; y++) {
            const child = document.createElement("div");
            child.classList.add("pixel");
            background.appendChild(child);
        }
    }

    const children = Array.from(background.getElementsByClassName("pixel"));

    children.forEach((child) => {
        const rect = child.getBoundingClientRect();
        const { data } = ctx.getImageData(rect.left, rect.top, pixel.size, pixel.size);

        const averageColor = getAverageColor(data);
        const color = `rgb(${averageColor}, ${averageColor}, ${averageColor})`;

        child.style.backgroundColor = color;
        child.style.boxShadow = `0 0 10px ${color}`;
    });
}

function getAverageColor(data) {
    let sum = 0;

    for (let i = 0; i < data.length; i += 4) {
        sum += data[i];
    }

    const averageColor = sum / (data.length / 4);

    return averageColor;
}

const starTXT = new Image(1600, 1600);
starTXT.src = "star.png";

starTXT.onload = resizeCanvas;

window.addEventListener('resize', resizeCanvas);

字符串
另一个问题是重复,所以它是最佳的和快速的,代码滞后很多。任何提示?任何不同的方法,我可以采取?
the result I aimed for
目前的实现似乎存在性能瓶颈。我怀疑draw函数中的循环和画布的频繁操作可能会导致速度变慢。我如何优化代码以提高效率?动画速度对于流畅的用户体验至关重要。我应该遵循哪些特定的技术或最佳实践来提高星落效果的速度?我使用画布绘制星星和像素操作背景的组合。这是最佳的方法,或者有其他方法可以提供更好的性能?
我已经尝试了一些基本的优化,比如使用requestAnimationFrame而不是setInterval,但我希望社区能提供更多的见解和建议。如果能帮助识别和解决代码中的性能问题,我将不胜感激。
对不起,如果我的代码是任何混乱或不可读,我已经尝试了很多。
编辑:HTML文件在下面

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>...</title>

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            height: 100vh;
            background-color: black;
            overflow: hidden;
            /* Prevent scrolling */
        }

        #bkg-content {
            width: 100%;
            height: 100%;
            position: absolute;
            z-index: 1;
            opacity: 0%;
        }

        #bkg {
            width: 100%;
            height: 100%;
            display: grid;
            gap: 3px;
            position: absolute;
            z-index: 2;
        }

        .pixel {
            width: 9px;
            height: 9px;
        }
    </style>
</head>

<body>
    <canvas id="bkg-content"></canvas>
    <div id="bkg"></div>
    <script src="./script.js"></script>
</body>

</html>

aydmsdu9

aydmsdu91#

获得良好动画速度的最佳方法是通过CSS Animation来实现,而不是尝试使用JS。
CSS动画的介绍有点超出了SO答案,所以这里是MDN上的主题介绍。
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations

ppcbkaq5

ppcbkaq52#

好吧,我做到了。
事实是你的代码并不是那么低效!它非常好。只是有两个问题:

  • 你是在重绘每一个像素而不是重用它们。我通过在createPixels函数外提取childrens数组来纠正这个问题。
  • 你人为地让它看起来像你的网站是滞后的,因为你的setInterval回调是如此之高。这是通过使用requestAnimationFrame没有延迟纠正的。

看看这个:https://jsfiddle.net/b7pv152w/
代码,供其他用户参考,可以在下面找到:

const content = document.getElementById("bkg-content");
const background = document.getElementById("bkg");
const ctx = content.getContext("2d", {
  willReadFrequently: true
});

let stars = [];
const maxStars = 20; // Maximum number of stars

let last_x = 0;
let next_spawn = 0;

function getX(size) {
  let random = Math.random() * (window.innerWidth - size);
  return last_x - size > random || random > last_x + size ? random : getX(size);
}

function createStar() {
  let transparency = Math.random();
  let size = transparency * 200 + 100;
  let x = getX(size)
  let fallingSpeed = Math.random() * 30 + 20;
  next_spawn = size / fallingSpeed * 500;

  last_x = x;
  return {
    x,
    y: 0,
    size,
    transparency,
    rotationAngle: 0,
    rotationSpeed: Math.random() * 0.3 + 0.05,
    rotationDirection: Math.random() < 0.5 ? 1 : -1,
    fallingSpeed,
  };
}

function spawnStars(count) {
  for (let i = 0; i < count; i++) {
    stars.push(createStar());
  }

  setTimeout(() => {
    spawnStars(1)
  }, next_spawn)

}

function draw() {
  ctx.clearRect(0, 0, content.width, content.height);

  for (let i = 0; i < stars.length; i++) {
    let star = stars[i];
    star.rotationAngle += star.rotationSpeed * star.rotationDirection;
    star.y += star.fallingSpeed;

    if (star.y > window.innerHeight + star.size / 2) {
      // Remove stars that have completely passed the bottom of the screen
      stars.splice(i, 1);
      i--; // Adjust the loop variable since the array length has changed
    } else {
      ctx.save();
      ctx.translate(star.x + star.size / 2, star.y);
      ctx.rotate(star.rotationAngle);

      // Adjust transparency based on star size
      ctx.globalAlpha = star.transparency;

      ctx.drawImage(starTXT, -star.size / 2, -star.size / 2, star.size, star.size);

      // Reset global alpha for subsequent drawings
      ctx.globalAlpha = 1;
      ctx.restore();
    }
  }
}

function resizeCanvas() {
  content.width = window.innerWidth;
  content.height = window.innerHeight;

  spawnStars(Math.min(maxStars, 1)); // Initially spawn one star

  setInterval(() => {
    createPixels()
    draw()
  }, 50);
}

let drawnPixels = false;
let children = []

function createPixels() {
  const pixel = {
    size: 9,
    gap: 3
  };
  const numX = Math.floor(window.innerWidth / (pixel.size + pixel.gap));
  const numY = Math.floor(window.innerHeight / (pixel.size + pixel.gap));

  background.style.gridTemplateColumns = `repeat(${numX}, ${pixel.size}px)`;

  if (!drawnPixels) {
    for (let x = 0; x < numX; x++) {
      for (let y = 0; y < numY; y++) {
        const child = document.createElement("div");
        child.classList.add("pixel");
        background.appendChild(child);
        children.push(child)
      }
    }
    drawnPixels = true;
  }

  children.forEach((child) => {
    const rect = child.getBoundingClientRect();
    const {
      data
    } = ctx.getImageData(rect.left, rect.top, pixel.size, pixel.size);

    const averageColor = getAverageColor(data);
    const color = `rgb(${averageColor}, ${averageColor}, ${averageColor})`;

    child.style.backgroundColor = color;
    child.style.boxShadow = `0 0 10px ${color}`;
  });
}

function getAverageColor(data) {
  let sum = 0;

  for (let i = 0; i < data.length; i += 4) {
    sum += data[i];
  }

  const averageColor = sum / (data.length / 4);

  return averageColor;
}

const starTXT = new Image(1600, 1600);

starTXT.src = "" //INSERT SOURCE HERE

starTXT.onload = resizeCanvas;

window.addEventListener('resize', resizeCanvas);

个字符

相关问题