css 调整大小性能缓慢,有大量DIV

m528fe3b  于 2023-05-08  发布在  其他
关注(0)|答案(4)|浏览(151)

bounty还有2天到期。回答此问题可获得+400声望奖励。Zoltán Matók想要引起更多关注这个问题:看起来像是一个现在应该有解决办法的问题。奇怪的是,还没有人提出。

我显示一个小的益智游戏的点网格。这个脚本还不太正确,但这不是问题所在。
问题是有了所有这些div,调整浏览器窗口的大小非常缓慢。尽管没有在调整大小时重绘/重新定位元素之类的事情。
我在Safari和Chrome上都试过了。用grid也试了一下,结果和绝对定位一样。

需要一个大窗口,以生成足够的div(~5000)来显示问题。

我们是不是不该有这么多的迪弗?有没有办法优化这一点?也许有一个替代的解决方案,有一个大的网格的元素?
使用three.js对于我想要完成的事情来说感觉非常大材小用。
谢谢大家。

let size = 10
let gap = 10
let cols = (window.innerWidth - gap) / (size + gap)
let rows = (window.innerHeight - gap) / (size + gap)
let col = 0
let row = 0
for (let i = 0; i < cols * rows; i++) {

  let div = document.createElement('div')
  document.body.appendChild(div)

  div.style.top = gap + row * (gap + size) + 'px'
  div.style.left = (row % 2 == 0 ? gap : 0) + gap + col * (gap + size) + 'px'
  div.style.animationDelay = (0.25 * i) + 's'

  col += 1
  if (col > cols) {
    col = 0;
    row += 1
  }
}
div {
  position: absolute;
  background: #adadad;
  width: 10px;
  height: 10px;
  border-radius: 5px;
}
r3i60tvu

r3i60tvu1#

发生此问题的原因是,在调整大小操作期间,浏览器在页面上执行多次重排。
在调整窗口大小时,浏览器执行完整的reflow并重绘DOM。回流是非常昂贵的过程,特别是对于大量节点。这也是一个用户阻塞过程(即,用户交互在发生时将被中断,并且将同步进行)。虽然一些回流(诸如由JavaScript在特定元素上触发的那些回流)可以在节点的子集上进行,但是窗口调整大小可以预期为DOM中的所有显示的节点触发回流。在某些硬件上,> 5000个节点的回流时间超过一帧(16 ms)并非不合理。拖动窗口边框来调整大小将触发一系列非常快速的回流,这些回流都是阻塞的,因此您看到性能问题是可以预料的。
浏览器确实有一些优化,以尽量减少不必要的回流,但不幸的是,看起来你可能不走运。例如,absolutefixed定位的元素有时可以免于回流,但在这种情况下,整个页面的几何形状正在更改,因此无论其位置属性如何,都会为所有节点触发回流。
对于您的问题,有两种可能的解决方法。

1.调整大小过程中隐藏节点。

设置display: none将从回流计算中删除元素及其子元素。在第一次出现resize事件时,将最外面的元素设置为display: none以隐藏游戏。回流计算应大大加快。调整大小完成后,设置display: block(或任何原始的display值)。您可以调整一个去抖动函数来确定调整大小何时完成--我发现50 ms没有新的resize事件是一个很好的指示器,表明用户已经停止调整大小。
这显然意味着在调整大小时会出现一个空白屏幕。为了获得更无缝的外观,您可能希望在调整大小过程中显示通知。或者你可以使用一个库,比如html2canvas,在删除游戏元素之前截取页面的屏幕截图,并在调整大小时显示出来。请注意,对于如此大量的节点,使用html 2canvas在捕获图像时可能会导致初始延迟。

2.使用画布

运行一个有数千个HTML节点的游戏肯定会突破HTML的极限。使用HTMLCanvas可能是更合适的方法,尽管听起来它需要对代码进行大量的修改。您可以控制画布重绘的每一个时机,因此调整窗口大小不需要任何重绘。在游戏中使用Canvas还有许多其他好处,例如它使用GPU而不是CPU来绘制,但这超出了这个问题的范围。

dl5txlt9

dl5txlt92#

确实有一个限制,这仅仅是Google的一个建议,以避免过多的DOM大小。但是对很多div的表现会有很大的影响。Ligthouse建议最多只有1400个节点。根据我的经验,一个超过2k节点的网页将正常工作。但是过量将具有明显的性能损失。每个用户的确切节点数量将不同,之后性能将降低。
https://developer.chrome.com/docs/lighthouse/performance/dom-size/
如果你想画一个像这样的交互式背景,我建议看看如何画画布。作为一种替代方法,webgl提供了利用增强的渲染质量为您的游戏的权力。

8i9zcol2

8i9zcol23#

程序不应该给予你恒定的滞后,但滞后可能仍然存在,这取决于你的电脑,但我做了它是当调整屏幕大小它应该隐藏球,直到你停止调整屏幕大小2秒钟你可以调整时间。我也不建议在这里运行它在堆栈溢出导致额外的滞后只是运行它在这个网站,而不是https://500.alexhernandez11.repl.co

<!DOCTYPE html>
<html>
<head>
    <title>5000 Balls Example</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.5.0-beta4/html2canvas.min.js"></script>
    <style>
    
    .container {
        position: absolute;
        width: 100%;
        height: 100%;
        background-color: black;
    top: 0px;
    right: 0px;
    bottom: 0px;
    left: 0px;
    }
    
    body {
        margin: 0px;
        height: 100%;
    }
    
        .ball {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            position: absolute;
            background-color: gray;
            animation: move 1s infinite linear;
            opacity: 0.7;
        }
    
        @keyframes move {
            0% {
                transform: translate(-50%, -50%) rotate(0deg) translateX(50%);
            }
            50% {
                transform: translate(-50%, -50%) rotate(180deg) translateX(50%);
            }
            100% {
                transform: translate(-50%, -50%) rotate(360deg) translateX(50%);
            }
        }
    </style>
</head>
<body>
    <span id="width"></span><br>
    <span id="height"></span>
    
    <div class="container">
        <!-- Generate balls with JS -->
    </div>
    
    

    
    <script>
        function generate_balls() {
          // Generate balls
          const numBalls = 5000;
          const container = document.querySelector('.container');
          for (let i = 0; i < numBalls; i++) {
            const ball = document.createElement('div');
            ball.className = 'ball';
            ball.style.left = `${Math.random() * 100}%`;
            ball.style.top = `${Math.random() * 100}%`;
            container.appendChild(ball);
          }
        }

        function takeScreenshot() {
            html2canvas(document.querySelector(".container")).then(canvas => {
            imgData = canvas.toDataURL('image/png');
            document.body.style.backgroundImage = `url(${imgData})`;
            });
        }

        let isResizing = false;
        let timeoutId = null;
        const myDiv = document.querySelector('.container');
        
        function resizeHandler() {
          isResizing = true;
          document.getElementById('width').textContent = window.innerWidth;
          document.getElementById('height').textContent = window.innerHeight;
          console.log("Resizing started");
        
          // this will remove the ball until resizing finsihed
          
         
          myDiv.style.display = "none"; // Hide the container
          //myDiv.style.display = imgData; 
          
          if (timeoutId) {
            clearTimeout(timeoutId);
          }
        
          timeoutId = setTimeout(function() {
            isResizing = false;
            timeoutId = null;
            console.log("Resizing finished");
        
            myDiv.style.display = "block"; // Show the container
            
          }, 2 * 1000); // Set timeout duration in seconds (1 second in this example)
        }
    
        window.addEventListener('resize', resizeHandler);
        
        generate_balls();
        takeScreenshot()
        
    </script>
</body>
</html>
khbbv19g

khbbv19g4#

for周期太长了,而且它有很多微任务,而不是那样,你可以尝试将一些逻辑移到CSS上。
CSS的执行速度比JavaScript快得多,样式定义应该通过CSS设置
在这里我尝试了一些不同的东西,我认为执行速度比你原来的代码快很多
注意:但如果你想做一个有很多元素的游戏,更好的选择是使用画布(像Google Sheets有很多元素),并在这个基础上构建你的游戏。也许你可以给予一下这样的图书馆:https://github.com/collections/javascript-game-engines

const start = Date.now();
    console.log('start execution', start);

    const size = 10;
    const gap = 10;
    const cols = (window.innerWidth - gap) / (size + gap);
    const rows = (window.innerHeight - gap) / (size + gap);

    const container = document.querySelector('.dots-container');

    const row = document.createElement('div');

    for (let col = 0; col < cols; col++) {
        row.className = "row-dots";
        const dot = document.createElement('div');
        dot.className = "dot";
        row.appendChild(dot);
    }

    const rowOdd = row.cloneNode(true);
    rowOdd.lastChild.remove();
    rowOdd.className = "row-odd-dots";

    for (let rowCount = 0; rowCount < rows; rowCount++) {
        if (rowCount % 2 === 0) {
            container.appendChild(row.cloneNode(true));
        } else {
            container.appendChild(rowOdd.cloneNode(true));
        }
    }

    const end = Date.now();
    console.log('end execution', end, 'total', end - start);
.dots-container {
    display: flex;
    height: 100%;
    width: 100%;
    gap: 10px;
    flex-direction: column;
}

.dots-container .dot {
    background-color: #adadad;
    width: 10px;
    height: 10px;
    border-radius: 999rem;
    display: flex;
}

.dots-container .row-dots,
.dots-container .row-odd-dots {
    display: flex;
    width: 100%;
    gap: 10px;
    justify-content: center;
}
<div class="dots-container"></div>

性能:
您的原始实现:在2、3和4毫秒之间(在大多数运行中为3和4)始终介于0和1毫秒之间
如果你在一个窗口调整回调函数中运行这个函数,添加一个类似dobounce的函数来每100 ms执行一次。
检查它在开始和结束时添加日志
希望有帮助,祝你好运!

相关问题