• 根据浏览器渲染引擎工作原理(reflow/repaint),来优化DOM的操作


    1.浏览器的渲染引擎工作原理:

      (1)解析HTML,生成DOM树。解析HTML文档,转换树中的html标签或js生成的标签到DOM节点,它被称为 -- 内容树。

      (2)构建渲染树,解析Style,生成Style Rules,解析CSS(包括外部CSS文件和样式元素以及js生成的样式),根据CSS选择器计算出节点的样式,创建另一个树 —- 渲染树。

      (3)根据(1)和(2)开始布局渲染树,从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标。

      (4)一旦布局渲染树结束后,接下来浏览器会将其绘制出来,遍历渲染树,每个节点将使用UI后端层来绘制。

       下图是工作原理截图:

    2.重绘与回流(Repaint/Reflow)

    repaint(重绘) :repaint发生更改时,元素的外观被改变,且在没有改变布局的情况下发生,如改变outline,visibility,background color,不会影响到dom结构渲染。
    reflow(渲染):与repaint区别就是他会影响到dom的结构渲染,同时他会触发repaint,他会改变他本身与所有父辈元素(祖先),这种开销是非常昂贵的,导致性能下降是必然的,页面元素越多效果越明显。

    3.引起reflow/repaint的操作

    Reflow 的成本比 Repaint 的成本高得多的多。DOM Tree 里的每个结点都会有 reflow 方法,一个结点的 reflow 很有可能导致子结点,甚至父点以及同级结点的 reflow。在一些高性能的电脑上也许还没什么,但是如果 reflow 发生在手机上,那么这个过程是非常痛苦和耗电的。
    所以,下面这些动作有很大可能会是成本比较高的。

    (1)当你增加、删除、修改 DOM 结点时,会导致 Reflow 或 Repaint。
    (2)当你移动 DOM 的位置,或是搞个动画的时候。
    (3)当你修改 CSS 样式的时候。
    (4)当你 Resize 窗口的时候(移动端没有这个问题),或是滚动的时候。
    (5)当你修改网页的默认字体时。
    (6)读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、 getComputedStyle()、currentStyle(in IE)

    注:display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。

    4.如何避免

    (1)避免一系列的连续操作

    demo1

    如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document   
    var fragment = document.createDocumentFragment();
    fragment.appendChild(document.createTextNode('keenboy test 111'));
    fragment.appendChild(document.createElement('br'));
    fragment.appendChild(document.createTextNode('keenboy test 222'));
    document.body.appendChild(fragment);

    demo2

    //先创建文档碎片
    var oFragmeng = document.createDocumentFragment();
    for(var i=0;i<10000;i++)
    {
    var op = document.createElement("span");
    var oText = document.createTextNode(i);
    op.appendChild(oText);
    //先附加在文档碎片中
    oFragmeng.appendChild(op);
    }
    //最后一次性添加到document中
    document.body.appendChild(oFragmeng);

    (2)先将元素从document中删除,完成修改后再把元素放回原来的位置

    (3)将元素的display设置为”none”,完成修改后再把display修改为原来的值

    (4)集中修改样式

    4.1尽可能少的修改元素style上的属性
      4.2尽量通过修改className来修改样式
      4.3通过cssText属性来设置样式值
        element.style.width=”80px”; //reflow
        element.style.height=”90px”; //reflow
        element.style.border=”solid 1px red”; //reflow
        以上就产生多次reflow,调用的越多产生就越多
        element.style.cssText=”80px;height:80px;border:solid 1px red;”; //reflow 
      4.4缓存经常访问的属性
        // 这样很不好
    for(big; loop; here) {
    el.style.left = el.offsetLeft + 10 + "px";
    el.style.top = el.offsetTop + 10 + "px";
    }

    // 这样会更好
    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";
    }
    多次使用left,top也就产生一次reflow
      4.5设置元素的position为absolute或fixed
        元素脱离标准流,也从DOM树结构中脱离出来,在需要reflow时只需要reflow自身与下级元素
      4.6尽量不要用table布局
    table元素一旦触发reflow就会导致table里所有的其它元素reflow。在适合用table的场合,
    可以设置table-layout为auto或fixed,这样可以让table一行一行的渲染,
    这种做法也是为了限制reflow的影响范围
      4.7避免使用expression,他会每次调用都会重新计算一遍(包括加载页面)

    引用github上的:

    减少回流和重绘

    好了,到了我们今天的重头戏,前面说了这么多背景和理论知识,接下来让我们谈谈如何减少回流和重绘。

    最小化重绘和重排

    由于重绘和重排可能代价比较昂贵,因此最好就是可以减少它的发生次数。为了减少发生次数,我们可以合并多次对DOM和样式的修改,然后一次处理掉。考虑这个例子

    const el = document.getElementById('test');
    el.style.padding = '5px';
    el.style.borderLeft = '1px';
    el.style.borderRight = '2px';

    例子中,有三个样式属性被修改了,每一个都会影响元素的几何结构,引起回流。当然,大部分现代浏览器都对其做了优化,因此,只会触发一次重排。但是如果在旧版的浏览器或者在上面代码执行的时候,有其他代码访问了布局信息(上文中的会触发回流的布局信息),那么就会导致三次重排。

    因此,我们可以合并所有的改变然后依次处理,比如我们可以采取以下的方式:

    • 使用cssText

      const el = document.getElementById('test');
      el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
    • 修改CSS的class

      const el = document.getElementById('test');
      el.className += ' active';

    批量修改DOM

    当我们需要对DOM对一系列修改的时候,可以通过以下步骤减少回流重绘次数:

    1. 使元素脱离文档流
    2. 对其进行多次修改
    3. 将元素带回到文档中。

    该过程的第一步和第三步可能会引起回流,但是经过第一步之后,对DOM的所有修改都不会引起回流重绘,因为它已经不在渲染树了。

    有三种方式可以让DOM脱离文档流:

    • 隐藏元素,应用修改,重新显示
    • 使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档。
    • 将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。

    考虑我们要执行一段批量插入节点的代码:

    function appendDataToElement(appendToElement, data) {
        let li;
        for (let i = 0; i < data.length; i++) {
        	li = document.createElement('li');
            li.textContent = 'text';
            appendToElement.appendChild(li);
        }
    }
    
    const ul = document.getElementById('list');
    appendDataToElement(ul, data);

    如果我们直接这样执行的话,由于每次循环都会插入一个新的节点,会导致浏览器回流一次。

    我们可以使用这三种方式进行优化:

    隐藏元素,应用修改,重新显示

    这个会在展示和隐藏节点的时候,产生两次回流

    function appendDataToElement(appendToElement, data) {
        let li;
        for (let i = 0; i < data.length; i++) {
        	li = document.createElement('li');
            li.textContent = 'text';
            appendToElement.appendChild(li);
        }
    }
    const ul = document.getElementById('list');
    ul.style.display = 'none';
    appendDataToElement(ul, data);
    ul.style.display = 'block';

    使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档

    const ul = document.getElementById('list');
    const fragment = document.createDocumentFragment();
    appendDataToElement(fragment, data);
    ul.appendChild(fragment);

    将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。

    const ul = document.getElementById('list');
    const clone = ul.cloneNode(true);
    appendDataToElement(clone, data);
    ul.parentNode.replaceChild(clone, ul);

    原因:原因其实上面也说过了,现代浏览器会使用队列来储存多次修改,进行优化,所以对这个优化方案,我们其实不用优先考虑。

    避免触发同步布局事件

    上文我们说过,当我们访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。举个例子,比如说我们想将一个p标签数组的宽度赋值为一个元素的宽度,我们可能写出这样的代码:

    function initP() {
        for (let i = 0; i < paragraphs.length; i++) {
            paragraphs[i].style.width = box.offsetWidth + 'px';
        }
    }

    这段代码看上去是没有什么问题,可是其实会造成很大的性能问题。在每次循环的时候,都读取了box的一个offsetWidth属性值,然后利用它来更新p标签的width属性。这就导致了每一次循环的时候,浏览器都必须先使上一次循环中的样式更新操作生效,才能响应本次循环的样式读取操作。每一次循环都会强制浏览器刷新队列。我们可以优化为:

    const width = box.offsetWidth;
    function initP() {
        for (let i = 0; i < paragraphs.length; i++) {
            paragraphs[i].style.width = width + 'px';
        }
    }

    对于复杂动画效果,使用绝对定位让其脱离文档流

    对于复杂动画效果,由于会经常的引起回流重绘,因此,我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流

  • 相关阅读:
    Ubuntu的防火墙UFW
    使用Xshell连接Ubuntu
    Markdown 11种基本语法
    Git Push 避免用户名和密码方法
    "git rm" 和 "rm" 的区别
    无限级分类实现思路
    1. Git 克隆代码
    Git 笔记
    git 远程分支创建与推送
    ci 笔记
  • 原文地址:https://www.cnblogs.com/liuhp/p/9736296.html
Copyright © 2020-2023  润新知