美文网首页
深入理解浏览器渲染原理:Repaint, Reflow

深入理解浏览器渲染原理:Repaint, Reflow

作者: 风起云涌Hal | 来源:发表于2017-01-04 00:26 被阅读2420次
    引用自 Rendering: repaint, reflow/relayout, restyle

    浏览器在下载好HTML、CSS、JS等文件后,是如何将这些内容组装成绚丽的页面呈现给用户呢?这儿我们可以深入了解一下这个过程:

    渲染过程

    不同的浏览器渲染过程实际上并不相同,但是依旧存在相一致的部分,大致过程如下图:


    render.png
    • 浏览器解析HTML的源码,然后构造出一个DOM(学名:文档对象模型)树,这棵树包含了所有的DOM结点与其中囊括的文本内容,在JS中,我们可以通过document中的一些方法(例如:getElementById, getElementsByTagName, querySelector...)拿到HTML结点所对应的DOM,并对其进行一些操作。一般来说,DOM树是以<html>作为根结点。
    • 接下来,浏览器开始对CSS文件内容进行解析。一般来说,浏览器会先查找内联样式,然后应用CSS文件中定义的样式,最后再是应用浏览器默认样式。
    • 然后,就是构造渲染树的过程。渲染树与DOM树有些类似,但并不完全相同。例如我们定义了一个<div style="display:none;"></div>的DOM,实际上这个结点并不会在渲染树里存在,类似的还有其他的不可见元素。另一方面,DOM中同一类型的结点可能会存在多个,每个结点实际上都是一个盒子,包括宽度、高度、边框大小、边距等等。
    • 一旦渲染树构造好了,接下来浏览器会将其绘制出来。

    森林和树

    我们先看一段HTML代码:

    <html>
    <head>
      <title>Beautiful page</title>
    </head>
    <body>
        
      <p>
        Once upon a time there was 
        a looong paragraph...
      </p>
      
      <div style="display: none">
        Secret message
      </div>
      
      <div>![](...)</div>
      ...
     
    </body>
    </html>
    

    其DOM树大致如此:

    documentElement (html)
        head
            title
        body
            p
                [text node]
            
            div 
                [text node]
            
            div
                img
            
            ...
    

    渲染树为DOM树中可视的部分:

    root (RenderView)
        body
            p
                line 1
                line 2
                line 3
                ...
            
            div
                img
            
        ...
    

    渲染树的根结点囊括了所有的可视元素,它是浏览器窗口的一部分,并且能够进行伸缩调整。一般来说,渲染区域为自浏览器左上角(0,0)起始,终止于右下角(window.innerWidth, window.innerHeight)的矩形部分。

    重绘与回流

    当第一次打开一个页面时,至少会有一次重绘和回流。之后,如果渲染树发生了变动,那么可能会触发重绘或回流中的一个或二者。

    1. 如果渲染树的结点发生了结构性变化,例如宽度、高度或者位置上的变化时,那么会触发Reflow(回流)的逻辑。我们第一次进入一个页面时便会至少触发一次这个逻辑。
    2. 如果渲染树结点发生了非结构性变化,例如背景色等的变化时,那么会触发Repaint(重绘)的逻辑。

    重绘与回流都会导致体验上的不佳。

    触发Repaint或Reflow

    我们具体看看哪些操作会导致重绘或回流:

    • 增加、删除、修改DOM结点
    • 使用display:none;的方式隐藏一个结点会导致repaint与reflow,使用visibility:hidden;进行dom隐藏仅仅导致repaint(没有结构性变化,仅仅看不见而已)
    • 移动dom或着该dom进行动画
    • 添加新的样式,或者修改某个样式
    • 用户的一些操作诸如改变浏览器窗口大小,调整字体大小,滚动等等

    我们看些例子:

    var bstyle = document.body.style; // cache
     
    bstyle.padding = "20px"; // reflow, repaint
    bstyle.border = "10px solid red"; // another reflow and a repaint
     
    bstyle.color = "blue"; // repaint only, no dimensions changed
    bstyle.backgroundColor = "#fad"; // repaint
     
    bstyle.fontSize = "2em"; // reflow, repaint
     
    // new DOM element - reflow, repaint
    document.body.appendChild(document.createTextNode('dude!'));
    

    有些reflow的操作代价会比一般的高出许多。可以试着想象出一棵渲染树,如果只是自上而下的对某些dom进行一些宽高等方面的调整,那么并不会导致整棵渲染树太大的变动,而如果是频繁调整部分结点在整个渲染树的位置,这种调整往往是“牵一而动全身”,代价就会非常高了。

    浏览器的处理方式

    既然渲染树的reflow或repaint的代价十分高昂,那么不得不采取一些优化的方式,浏览器对此有一些针对性的举措。一种策略便是延迟。浏览器会将一些变动放在一个队列中,当达到一定规模或者延迟的时间已到,那么会一次将这些变动反应到渲染树中。但是这种策略会有一定的弊端,当我们执行一些脚本时可能会导致浏览器不得不提前让repaint或reflow进行完毕,例如我们需要获取一些样式信息时,诸如:

    1. offsetTop, offsetLeft, offsetWidth, offsetHeight
    2. scrollTop/Left/Width/Height
    3. clientTop/Left/Width/Height
    4. getComputedStyle(), or currentStyle in IE
        为了让JS获取到最终的样式,浏览器不得不将缓冲队列里reflow或repaint过程执行完毕。所以,我们一般需要避免一连串的设置或获取dom样式:
    // no-no!
    el.style.left = el.offsetLeft + 10 + "px";
    

    压缩repaints或reflows

    我们有一些策略去尽量消除或减小reflow/repaint所带来的负面影响。

    • 不要一个一个地去改变结点的样式,而可以通过设置cssText一次性将结点样式修改完毕:
    // bad
    var left = 10,
        top = 10;
    el.style.left = left + "px";
    el.style.top  = top  + "px";
     
    // better 
    el.className += " theclassname";
     
    // or when top and left are calculated dynamically...
     
    // better
    el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
    
    • 对多个dom进行操作时,我们可以使用一种“离线”方式。“离线”意味着我们我们先在渲染树之外进行操作。

      • 创建一个documentFragment去保持住我们要操作的dom
      • 克隆你需要进行操作的结点,进行操作后再将其与原始结点作交换
    • 不要经常去访问计算后的样式,如果可以,可以先将这些信息缓存下来。

    // no-no!
    for(big; loop; here) {
        el.style.left = el.offsetLeft + 10 + "px";
        el.style.top  = el.offsetTop  + 10 + "px";
    }
     
    // better
    var left = el.offsetLeft,
        top  = el.offsetTop
        esty = el.style;
    for(big; loop; here) {
        left += 10;
        top  += 10;
        esty.left = left + "px";
        esty.top  = top  + "px";
    }
    
    • 一般说来,如果对一个使用了绝对布局的dom进行操作,并不会导致大量的reflow,不过绝对布局丧失了许多灵活性,很少有人会使用绝对定位的方式进行主界面的布局。

    相关文章

      网友评论

          本文标题:深入理解浏览器渲染原理:Repaint, Reflow

          本文链接:https://www.haomeiwen.com/subject/njjnvttx.html