>  기사  >  웹 프론트엔드  >  페이지 다시 그리기 및 리플로우를 위한 최적화 방법

페이지 다시 그리기 및 리플로우를 위한 최적화 방법

一个新手
一个新手원래의
2017-09-18 09:34:493139검색

토론 페이지가 다시 그려지고 리플로우되기 전입니다. 페이지 렌더링 프로세스, 페이지가 CSS와 결합된 HTML을 브라우저에 표시하는 방법 등을 어느 정도 이해해야 합니다. 다음 순서도는 페이지 렌더링을 위한 브라우저의 처리 흐름을 보여줍니다. 브라우저마다 약간 다를 수 있습니다. 그러나 기본적으로 그들은 유사합니다.
페이지 다시 그리기 및 리플로우를 위한 최적화 방법

  1. 브라우저는 획득한 HTML 코드를 DOM 트리로 구문 분석합니다. HTML의 각 태그는 DOM 트리의 노드이고 루트 노드는 일반적으로 사용되는 문서 객체입니다. DOM 트리에는 표시:숨김 없음, JS를 사용하여 동적으로 추가된 요소 등을 포함한 모든 HTML 태그가 포함되어 있습니다.

  2. 브라우저는 모든 스타일(사용자 정의 CSS 및 사용자 에이전트)을 스타일 구조로 구문 분석합니다. 구문 분석 중에 브라우저가 인식할 수 없는 스타일은 제거됩니다. 예를 들어 IE는 -moz로 시작하는 스타일을 제거합니다. _로 시작하는 FF 스타일은 제거됩니다.

3. DOM 트리와 스타일 구조가 결합되어 렌더 트리는 DOM 트리와 유사하지만 그 차이는 매우 큽니다. 렌더 트리에는 고유한 스타일이 있으며, 렌더 트리에는 숨겨진 노드(display:none 노드 및 헤드 노드 등)가 포함되어 있지 않습니다. 이러한 노드는 렌더링에 사용되지 않고 렌더링에 영향을 주지 않으므로 포함되지 않기 때문입니다. 렌더 트리. visible:hidden이 레이아웃과 점유 공간에 영향을 미치기 때문에 visible:hidden에 의해 숨겨진 요소는 여전히 렌더 트리에 포함됩니다. CSS2 표준에 따르면 렌더 트리의 각 노드를 상자(상자 차원)라고 하며 페이지 요소는 패딩, 여백, 테두리 및 위치가 있는 상자로 이해됩니다.

  1. 렌더 트리가 구축되면 브라우저는 렌더 트리를 기반으로 페이지를 그릴 수 있습니다.

리플로우 및 다시 그리기

  1. 요소의 크기, 레이아웃, 숨기기 등의 변경으로 인해 렌더 트리의 일부(또는 전체)를 다시 작성해야 하는 경우. 이것을 리플로우라고 합니다. 모든 페이지는 페이지가 처음 로드될 때 적어도 한 번은 리플로우되어야 합니다. 리플로우 동안 브라우저는 렌더링 트리의 영향을 받은 부분을 무효화하고 리플로우를 완료한 후 영향을 받은 부분을 화면에 다시 그립니다. 이 프로세스를 다시 그리기라고 합니다.

  2. 렌더 트리의 일부 요소가 속성을 업데이트해야 하는 경우 이러한 속성은 요소의 모양과 스타일에만 영향을 미치고 배경색과 같은 레이아웃에는 영향을 미치지 않습니다. 다시 그리는 일이라고 합니다.

참고: 리플로우로 인해 확실히 다시 그리기가 발생하지만 다시 그리기로 인해 반드시 리플로우가 발생하지는 않습니다.

리플로우가 발생할 때:

페이지 레이아웃과 기하학적 속성이 변경될 때 리플로우가 필요합니다. 브라우저 리플로우는 다음 상황에서 발생합니다:

2. 요소 위치 변경

3. 여백, 패딩, 테두리, 너비 및 높이

. 콘텐츠 변경 - 텍스트 변경 또는 이미지 크기 변경으로 인한 계산된 값 너비 및 높이 변경

6. 브라우저 창 크기 변경 - 크기 조정 이벤트 발생 시 다음 코드가 리플로우와 다시 그리기에 어떤 영향을 미치는지 살펴보세요.

var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));

이에 대해 말하면 리플로우 비용은 다시 작성해야 하는 렌더 트리의 노드 수와 관련이 있다는 것을 누구나 알고 있습니다. 예를 들어 몸체 앞에 요소를 삽입하면 전체 렌더 트리가 리플로우되어 비용이 더 많이 들지만 몸체 뒤에 요소를 삽입하면 영향을 미치지 않습니다. 이전 요소의 리플로우

스마트 브라우저

이전 예제 코드에서 간단한 JS 코드 몇 줄로 인해 약 6번의 리플로우와 다시 그리기가 발생한 것을 볼 수 있습니다. 그리고 우리는 리플로우 비용이 적지 않다는 것도 알고 있습니다. 모든 JS 작업을 리플로우하고 다시 그려야 한다면 브라우저가 이를 감당하지 못할 수도 있습니다. 따라서 많은 브라우저는 이러한 작업을 최적화합니다. 브라우저는 대기열을 유지하고 리플로우 및 다시 그리기를 유발하는 모든 작업을 이 대기열에 넣습니다. 대기열의 작업이 특정 수 또는 특정 시간 간격에 도달하면 대기열이 플러시되고 배치가 처리됩니다. 이렇게 하면 여러 리플로우 및 다시 그리기가 하나의 리플로우 및 다시 그리기로 전환됩니다.

브라우저 최적화가 있더라도 때로는 우리가 작성하는 일부 코드로 인해 브라우저가 미리 대기열을 플러시하도록 강제할 수 있으므로 브라우저 최적화가 효과적이지 않을 수 있습니다. 브라우저에서 일부 스타일 정보를 요청하면 브라우저는 다음과 같이 대기열을 비웁니다.

offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
width,height
请求了getComputedStyle(), 或者 IE的 currentStyle

위 속성 중 일부를 요청할 때 가장 정확한 값을 제공하기 위해 브라우저는 대기열 이러한 값에 영향을 미치는 작업이 대기열에 있을 수 있으므로 플러시해야 합니다. 요소에 대해 얻은 레이아웃 및 스타일 정보가 최근에 발생했거나 변경된 레이아웃 정보와 아무 관련이 없더라도 브라우저는 강제로 렌더링 대기열을 새로 고칩니다.

    리플로우를 줄이고 다시 그리는 방법
  1. 减少回流、重绘 其实就是需要减少对 render tree 的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:

    1. 直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器)

    // 不好的写法var left = 1;var top = 1;
    el.style.left = left + "px";
    el.style.top = top + "px";// 比较好的写法el.className += " className1";// 比较好的写法el.style.cssText += "; 
    left: " + left + "px; 
    top: " + top + "px;";
    1. 让要操作的元素进行”离线处理”,处理完后一起更新

    a) 使用 DocumentFragment 进行缓存操作,引发一次回流和重绘;
    b) 使用 display:none 技术,只引发两次回流和重绘;
    c) 使用  cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;

    3.不要经常访问会引起浏览器 flush 队列的属性,如果你确实要访问,利用缓存

    // 别这样写,大哥
    for(循环) {
    el.style.left = el.offsetLeft + 5 + "px";el.style.top = el.offsetTop + 5 + "px";}
    
    // 这样写好点
    var left = el.offsetLeft,
    top = el.offsetTop,
    s = el.style; for (循环) { 
    left += 10; top += 10; s.left = left + "px"; s.top = top + "px"; }
    1. 让元素脱离动画流,减少回流的Render Tree的规模

    $("#block1").animate({left:50});
    $("#block2").animate({marginLeft:50});

    实例测试

    最后用2个工具对上面的理论进行一些测试,分别是:dynaTrace(测试ie),Speed Tracer(测试Chrome)。

    第一个测试代码不改变元素的规则,大小,位置。只改变颜色,所以不存在回流,仅测试重绘,代码如下:

    <body>
        <script type="text/javascript">
            var s = document.body.style;        var computed;        if (document.body.currentStyle) {
              computed = document.body.currentStyle;
            } else {
              computed = document.defaultView.getComputedStyle(document.body, &#39;&#39;);
            }    function testOneByOne(){
          s.color = &#39;red&#39;;;
          tmp = computed.backgroundColor;
          s.color = &#39;white&#39;;
          tmp = computed.backgroundImage;
          s.color = &#39;green&#39;;
          tmp = computed.backgroundAttachment;
        }    function testAll() {
          s.color = &#39;yellow&#39;;
          s.color = &#39;pink&#39;;
          s.color = &#39;blue&#39;;
    
          tmp = computed.backgroundColor;
          tmp = computed.backgroundImage;
          tmp = computed.backgroundAttachment;
        }    </script>    
        color test <br />
        <button onclick="testOneByOne()">Test One by One</button>
        <button onclick="testAll()">Test All</button></body>

    testOneByOne 函数改变3次color,其中每次改变后调用getComputedStyle,读取属性值(按我们上面的讨论,这里会引起队列的 flush),testAll 同样是改变3次color,但是每次改变后并不马上调用getComputedStyle。
    我们先点击Test One by One按钮,然后点击 Test All,用dynaTrace监控如下:
    페이지 다시 그리기 및 리플로우를 위한 최적화 방법

    上图可以看到我们执行了2次button的click事件,每次click后都跟一次rendering,2次click函数执行的时间都差不多, 0.25ms,0.26ms,但其后的rendering时间就相差一倍多。(其实很多时候,前端的性能瓶颈并不在于JS的执行,而是在于页面的呈现,这种情况在富客户端中更为突出)。我们再看图的下面部分,这是第一次rendering的详细信息,可以看到里面有2行是 Scheduleing layout task,这个就是我们前面讨论过的浏览器优化过的队列,可以看出我们引发2次的flush。

    페이지 다시 그리기 및 리플로우를 위한 최적화 방법

    再看第二次rendering的详细信息,可以看出并没有Scheduleing layout task,所以这次rendering的时间也比较短。

    测试代码2:这个测试跟第一次测试的代码很类似,但加上了对layout的改变,为的是测试回流。

     <script type="text/javascript">
            var s = document.body.style;        var computed;        if (document.body.currentStyle) {
              computed = document.body.currentStyle;
            } else {
              computed = document.defaultView.getComputedStyle(document.body, &#39;&#39;);
            }    function testOneByOne(){
          s.color = &#39;red&#39;;
          s.padding = &#39;1px&#39;;
          tmp = computed.backgroundColor;
          s.color = &#39;white&#39;;
          s.padding = &#39;2px&#39;;
          tmp = computed.backgroundImage;
          s.color = &#39;green&#39;;
          s.padding = &#39;3px&#39;;
          tmp = computed.backgroundAttachment;
        }    function testAll() {
          s.color = &#39;yellow&#39;;
          s.padding = &#39;4px&#39;;
          s.color = &#39;pink&#39;;
          s.padding = &#39;5px&#39;;
          s.color = &#39;blue&#39;;
          s.padding = &#39;6px&#39;;
    
          tmp = computed.backgroundColor;
          tmp = computed.backgroundImage;
          tmp = computed.backgroundAttachment;
        }    </script>  
    
        color test <br />
        <button onclick="testOneByOne()">Test One by One</button>
        <button onclick="testAll()">Test All</button>

    페이지 다시 그리기 및 리플로우를 위한 최적화 방법

    这图可以看出,有了回流后,rendering的时间相比之前的只重绘,时间翻了3倍了,可见回流的高成本性啊。

    大家看到时候注意明细处相比之前的多了个 Calcalating flow layout。

    最后再使用Speed Tracer测试一下,其实结果是一样的,只是让大家了解下2个测试工具:

    测试1:
    페이지 다시 그리기 및 리플로우를 위한 최적화 방법

    图上第一次点击执行2ms (其中有50% 用于style Recalculation), 第二次1ms,而且第一次click后面也跟了2次style Recalculation,而第二次点击却没有style Recalculation。
    但是这次测试发现paint重绘的时间竟然是一样的,都是3ms,这可能就是chrome比IE强的地方吧。

    测试2:
    페이지 다시 그리기 및 리플로우를 위한 최적화 방법

    从图中竟然发现第二次的测试结果在时间上跟第一次的完全一样,这可能是因为操作太少,而chrome又比较强大,所以没能测试明显结果出来。

    但注意图中多了1个紫色部分,就是layout的部分。也就是我们说的回流。

위 내용은 페이지 다시 그리기 및 리플로우를 위한 최적화 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.