html浏览器渲染引起回流的属性和方法,看这篇就够了

450 阅读6分钟

在Web开发中,浏览器渲染网页的过程涉及多个步骤,其中“回流”(Reflow)和“重绘”(Repaint)是两个重要的概念。了解这两个过程有助于优化网页性能,减少不必要的渲染开销。

1 回流定义(Reflow)

定义:回流是指浏览器为了重新计算元素的几何属性(如位置和大小)而进行的操作。当DOM树中的元素发生改变时,浏览器需要重新计算这些元素的位置和大小,以确保页面布局的正确性。

触发回流的常见情况

  • 添加或删除可见的DOM元素。
  • 元素的尺寸发生变化(如宽度、高度、字体大小等)。
  • 元素的内容发生变化(如文本内容增加或减少)。
  • CSS类的变化。
  • 计算样式(如调用 offsetWidthoffsetHeight)。
  • 页面滚动。

回流的影响:回流是一个相对昂贵的操作,因为它涉及到重新计算布局,可能会导致整个页面或部分页面的重新渲染。频繁的回流会严重影响页面性能。

2 重绘定义(Repaint)

定义:重绘是指浏览器为了更新元素的外观(如颜色、背景等)而进行的操作。当元素的样式发生变化但不影响其几何属性时,浏览器只需要重新绘制该元素即可。

触发重绘的常见情况

  • 改变元素的颜色或背景色。
  • 改变元素的可见性(如 visibility 属性)。
  • 改变元素的文本内容。
  • 改变元素的阴影、边框等不涉及几何属性的样式。

重绘的影响:重绘通常比回流便宜,因为它不需要重新计算布局。然而,频繁的重绘仍然会影响性能,尤其是在复杂页面中。

3 优化建议

  1. 批量操作DOM:尽量减少对DOM的频繁操作,可以将多个操作合并成一次操作。例如,使用 documentFragment 来批量插入多个元素。

  2. 避免强制同步布局:避免在循环中多次读取和修改布局相关的属性,这会导致浏览器进行多次回流。可以先读取所有需要的属性,然后再进行修改。

  3. 使用CSS动画:使用CSS动画而不是JavaScript来实现动画效果,因为CSS动画可以在GPU上进行,性能更好。

  4. 使用 requestAnimationFrame:在JavaScript中进行布局相关的操作时,使用 requestAnimationFrame 可以确保操作在下一个重绘周期之前完成,从而减少不必要的回流和重绘。

  5. 避免使用表格布局:表格布局的回流成本较高,因为一个单元格的变化可能会影响整个表格的布局。尽量使用更灵活的布局方式,如Flexbox或Grid。

示例

以下是一个简单的示例,展示了如何通过批量操作DOM来减少回流和重绘:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Optimize Reflow and Repaint</title>
    <style>
        .container {
            display: flex;
            flex-direction: column;
        }
        .item {
            height: 50px;
            margin: 10px;
            background-color: #4CAF50;
        }
    </style>
</head>
<body>
    <div class="container" id="container"></div>

    <script>
        function addItems() {
            const container = document.getElementById('container');
            const fragment = document.createDocumentFragment();

            for (let i = 0; i < 1000; i++) {
                const item = document.createElement('div');
                item.className = 'item';
                fragment.appendChild(item);
            }

            container.appendChild(fragment);
        }

        addItems();
    </script>
</body>
</html>

在这个示例中,我们使用 documentFragment 来批量插入多个元素,从而减少对DOM的频繁操作,提高性能。

更好地理解回流和重绘的概念,并在实际开发中进行优化。

4 引起回流的属性和方法

在Web开发中,某些CSS属性和JavaScript方法会触发浏览器的回流(Reflow)。了解这些属性和方法有助于优化页面性能,减少不必要的回流次数。以下是常见的引起回流的属性和方法:

4.1 引起回流的CSS属性

  1. 布局相关的属性

    • widthheight
    • marginpadding
    • border-width
    • top, right, bottom, left
    • min-width, max-width, min-height, max-height
    • display
    • position
    • float
    • box-sizing
    • transform(某些情况下,特别是涉及 translate 以外的变换)
  2. 内容相关的属性

    • font-size
    • line-height
    • text-indent
    • word-spacing
    • letter-spacing
  3. 其他影响布局的属性

    • overflow
    • z-index
    • visibility(从 hiddenvisible 或反之)

4.2 引起回流的JavaScript方法

  1. 获取布局信息的方法

    • offsetTop, offsetLeft, offsetWidth, offsetHeight
    • clientTop, clientLeft, clientWidth, clientHeight
    • scrollTop, scrollLeft, scrollWidth, scrollHeight
    • getBoundingClientRect()
    • getComputedStyle()
  2. 修改DOM结构的方法

    • appendChild()
    • insertBefore()
    • removeChild()
    • replaceChild()
  3. 修改样式的方法

    • element.style.property = value
    • classList.add(), classList.remove(), classList.toggle()
    • setAttribute('style', '...')
  4. 其他引起回流的方法

    • resize()(窗口大小改变)
    • scroll()(滚动事件)
    • focus()(焦点事件)

4.3 为什么这些方法会引起回流?

当调用这些方法时,浏览器需要确保返回的值是最新的,因此它会检查是否有任何布局变化需要重新计算。如果确实有变化,浏览器会进行回流以确保返回的值是准确的。

以下是一个简单的示例,展示了如何在循环中多次调用这些方法会引发多次回流:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Reflow Example</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #4CAF50;
        }
    </style>
</head>
<body>
    <div class="box" id="box"></div>

    <script>
        function measureBox() {
            const box = document.getElementById('box');
            for (let i = 0; i < 100; i++) {
                const width = box.offsetWidth; // 每次调用都会触发回流
                console.log(`Width: ${width}px`);
            }
        }

        measureBox();
    </script>
</body>
</html>

在这个示例中,每次调用 box.offsetWidth 都会触发一次回流,因为浏览器需要确保返回的宽度值是最新的。

4.4 优化建议

  1. 避免批量操作DOM

    • 尽量减少对DOM的频繁操作,可以将多个操作合并成一次操作。例如,使用 documentFragment 来批量插入多个元素。
  2. 避免强制同步布局

    • 避免在循环中多次读取和修改布局相关的属性,这会导致浏览器进行多次回流。可以先读取所有需要的属性,然后再进行修改。
  3. 使用CSS动画

    • 使用CSS动画而不是JavaScript来实现动画效果,因为CSS动画可以在GPU上进行,性能更好。
  4. 使用 requestAnimationFrame

    • 在JavaScript中进行布局相关的操作时,使用 requestAnimationFrame 可以确保操作在下一个重绘周期之前完成,从而减少不必要的回流和重绘。
    • 在JavaScript中进行布局相关的操作时,使用 requestAnimationFrame 可以确保操作在下一个重绘周期之前完成,从而减少不必要的回流和重绘。
    function measureBox() {
        const box = document.getElementById('box');
    
        requestAnimationFrame(() => {
            const width = box.offsetWidth; // 一次性读取
            const height = box.offsetHeight; // 一次性读取
    
            for (let i = 0; i < 100; i++) {
                console.log(`Width: ${width}px, Height: ${height}px`);
            }
        });
    }
    
    measureBox();
    
  5. 避免使用表格布局

    • 表格布局的回流成本较高,因为一个单元格的变化可能会影响整个表格的布局。尽量使用更灵活的布局方式,如Flexbox或Grid。
  6. 避免在循环中读取布局信息

    • 尽量避免在循环中多次读取布局信息,这会导致多次回流。可以将读取操作移到循环外部。
    • 如果需要多次读取布局信息,可以先将所有需要的值一次性读取出来,然后再进行后续操作。
    function measureBox() {
        const box = document.getElementById('box');
        const width = box.offsetWidth; // 一次性读取
        const height = box.offsetHeight; // 一次性读取
    
        for (let i = 0; i < 100; i++) {
            console.log(`Width: ${width}px, Height: ${height}px`);
        }
    }
    
    measureBox();
    

4.5 总结

获取布局信息的方法确实会触发回流,因为浏览器需要确保返回的值是最新的。通过优化读取布局信息的方式,可以显著减少不必要的回流,提高页面性能。

5 引起重绘的属性和方法

在Web开发中,某些CSS属性和JavaScript方法会触发浏览器的重绘(Repaint)。了解这些属性和方法有助于优化页面性能,减少不必要的重绘次数。以下是常见的引起重绘的属性和方法:

5.1 引起重绘的CSS属性

  1. 颜色和背景相关的属性

    • color
    • background-color
    • background-image
    • border-color
    • outline-color
    • text-decoration-color
    • text-shadow
  2. 文本和字体相关的属性

    • font-family
    • font-weight
    • font-style
    • text-align
    • text-transform
    • white-space
    • word-break
    • word-wrap
  3. 透明度和滤镜相关的属性

    • opacity
    • filter
  4. 可见性相关的属性

    • visibility
    • display(从 none 到其他值或反之)
  5. 其他影响外观的属性

    • box-shadow
    • border-radius
    • clip
    • cursor
    • list-style-image

5.2 引起重绘的JavaScript方法

  1. 颜色和背景相关的属性

    • window.getComputedStyle(element).color
    • window.getComputedStyle(element).backgroundColor
    • window.getComputedStyle(element).borderColor
    • window.getComputedStyle(element).textDecorationColor
    • window.getComputedStyle(element).textShadow
  2. 文本和字体相关的属性

    • window.getComputedStyle(element).fontFamily
    • window.getComputedStyle(element).fontWeight
    • window.getComputedStyle(element).fontStyle
    • window.getComputedStyle(element).textAlign
    • window.getComputedStyle(element).textTransform
    • window.getComputedStyle(element).whiteSpace
    • window.getComputedStyle(element).wordBreak
    • window.getComputedStyle(element).wordWrap
  3. 透明度和滤镜相关的属性

    • window.getComputedStyle(element).opacity
    • window.getComputedStyle(element).filter
  4. 可见性相关的属性

    • window.getComputedStyle(element).visibility
    • window.getComputedStyle(element).display
  5. 其他影响外观的属性

    • window.getComputedStyle(element).boxShadow
    • window.getComputedStyle(element).borderRadius
    • window.getComputedStyle(element).clip
    • window.getComputedStyle(element).cursor
    • window.getComputedStyle(element).listStyleImage
  6. 修改样式的方法

    • element.style.property = value
    • classList.add(), classList.remove(), classList.toggle()
    • setAttribute('style', '...')
  7. 修改DOM内容的方法

    • innerHTML
    • textContent
    • innerText
  8. 其他引起重绘的方法

    • scrollIntoView()
    • focus()
    • blur()

以下是一个简单的示例,展示了如何通过批量修改样式来减少重绘次数:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Optimize Repaint</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #4CAF50;
            color: white;
            text-align: center;
            line-height: 100px;
        }
        .new-class {
            background-color: red;
            color: black;
        }
    </style>
</head>
<body>
    <div class="box" id="box">Hello</div>

    <script>
        function changeStyle() {
            const box = document.getElementById('box');
            box.classList.add('new-class'); // 一次性修改多个样式
        }

        changeStyle();
    </script>
</body>
</html>

在这个示例中,我们通过添加一个CSS类来一次性修改多个样式,从而减少重绘次数。

5.3 为什么这些方法可能会引起重绘?

虽然获取这些属性和方法本身不会直接引起重绘,但在某些情况下,浏览器为了确保返回的值是最新的,可能会进行重绘。例如,如果你在一个动画循环中频繁获取这些属性,浏览器可能会在每次获取时进行重绘以确保返回的值是最新的。

以下是一个简单的示例,展示了如何在循环中多次获取样式属性可能会导致重绘:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Repaint Example</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #4CAF50;
            color: white;
            text-align: center;
            line-height: 100px;
        }
    </style>
</head>
<body>
    <div class="box" id="box">Hello</div>

    <script>
        function measureBox() {
            const box = document.getElementById('box');
            for (let i = 0; i < 100; i++) {
                const color = window.getComputedStyle(box).color; // 每次调用可能会引起重绘
                console.log(`Color: ${color}`);
            }
        }

        measureBox();
    </script>
</body>
</html>

在这个示例中,每次调用 window.getComputedStyle(box).color 都可能会引起一次重绘,因为浏览器需要确保返回的颜色值是最新的。

5.4 优化建议

  1. 批量读取样式信息

    • 如果需要多次读取样式信息,可以先将所有需要的值一次性读取出来,然后再进行后续操作。
    function measureBox() {
        const box = document.getElementById('box');
        const style = window.getComputedStyle(box); // 一次性读取
        const color = style.color;
        const backgroundColor = style.backgroundColor;
    
        for (let i = 0; i < 100; i++) {
            console.log(`Color: ${color}, Background Color: ${backgroundColor}`);
        }
    }
    
    measureBox();
    
  2. 批量修改样式

    • 尽量减少对样式的频繁修改,可以将多个样式修改合并成一次操作。例如,使用CSS类来批量修改样式。
    function changeStyle() {
        const element = document.getElementById('myElement');
        element.classList.add('new-class'); // 一次性添加多个样式
    }
    function changeText() {
        const elements = document.querySelectorAll('.text-element');
        const newContent = 'New Content';
    
        elements.forEach(element => {
            element.textContent = newContent; // 一次性修改
        });
    }
    
  3. 使用 requestAnimationFrame

    • 在JavaScript中进行样式相关的操作时,使用 requestAnimationFrame 可以确保操作在下一个重绘周期之前完成,从而减少不必要的重绘。
    function measureBox() {
        const box = document.getElementById('box');
    
        requestAnimationFrame(() => {
            const style = window.getComputedStyle(box); // 一次性读取
            const color = style.color;
            const backgroundColor = style.backgroundColor;
    
            for (let i = 0; i < 100; i++) {
                console.log(`Color: ${color}, Background Color: ${backgroundColor}`);
            }
        });
    }
    
    measureBox();
    
  4. 使用CSS动画

    • 使用CSS动画而不是JavaScript来实现动画效果,因为CSS动画可以在GPU上进行,性能更好。
    .animate {
        transition: background-color 0.5s ease;
    }
    
    function startAnimation() {
        const element = document.getElementById('myElement');
        element.classList.add('animate');
        element.style.backgroundColor = 'red';
    }
    
  5. 使用 requestAnimationFrame

    • 在JavaScript中进行样式相关的操作时,使用 requestAnimationFrame 可以确保操作在下一个重绘周期之前完成,从而减少不必要的重绘。
    function changeStyle() {
        const element = document.getElementById('myElement');
    
        requestAnimationFrame(() => {
            element.style.color = 'blue';
            element.style.backgroundColor = 'yellow';
        });
    }
    

5.5 总结

获取某些CSS属性和JavaScript方法本身不会直接引起重绘,但在某些情况下,浏览器为了确保返回的值是最新的,可能会进行重绘。通过优化读取样式信息的方式,可以显著减少不必要的重绘,提高页面性能。

PS:学会了记得,点赞,评论,收藏,分享