• 浏览器回流与重绘


    网页生成过程

    1. HTML被HTML解析器解析成DOM 树
    2. css则被css解析器解析成CSSOM 树
    3. 结合DOM树和CSSOM树,生成一棵渲染树(Render Tree)
    4. 生成布局(flow),即将所有渲染树的所有节点进行平面合成
    5. 将布局绘制(paint)在屏幕上

    第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染。

    网上找了一张图片,我加了注释会更直观一些:

    回流比重绘大

    • 回流:某些元素的外观被改变,例如:元素的填充颜色
    • 重排:重新生成布局,重新排列元素。

    单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。

    • 重绘"不一定会出现回流,回流必然会出现重绘

    渲染

    • 网页生成的时候,至少会渲染一次。
    • 在用户访问的过程中,还会不断重新渲染
    • 重新渲染需要重复之前的第四步(重新生成布局)+第五步(重新绘制)或者只有第五个步(重新绘制)。

    回流 (Reflow)

    当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

    会导致回流的操作:

    • 页面首次渲染
    • 浏览器窗口大小发生改变
    • 元素尺寸或位置发生改变
    • 元素内容变化(文字数量或图片大小等等)
    • 元素字体大小变化
    • 添加或者删除可见的DOM元素
    • 激活CSS伪类(例如::hover)
    • 查询某些属性或调用某些方法

    一些常用且会导致回流的属性和方法:

    • clientWidth、clientHeight、clientTop、clientLeft
    • offsetWidth、offsetHeight、offsetTop、offsetLeft
    • scrollWidth、scrollHeight、scrollTop、scrollLeft
    • scrollIntoView()、scrollIntoViewIfNeeded()
    • getComputedStyle()
    • getBoundingClientRect()
    • scrollTo()

    重绘 (Repaint)

    当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

    性能影响

    • 回流比重绘的代价要更高。

    有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。

    现代浏览器会对频繁的回流或重绘操作进行优化

    浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

    当你访问以下属性或方法时,浏览器会立刻清空队列:

    • clientWidth、clientHeight、clientTop、clientLeft
    • offsetWidth、offsetHeight、offsetTop、offsetLeft
    • scrollWidth、scrollHeight、scrollTop、scrollLeft
    • width、height
    • getComputedStyle()
    • getBoundingClientRect()

    因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。

    触发重排的因素

    添加或删除可见的DOM元素、改变元素位置、元素的尺寸改变(包括:外边距、内边距、边框、高度等)、内容改变(如:文本改变或图片被另外一个不同尺寸的图片替代)、浏览器窗口尺寸的改变、通过display: none隐藏⼀个DOM节点等

    触发重绘的因素

    重排必定触发重绘(重要)、通过visibility: hidden隐藏⼀个DOM节点、修改元素背景色、修改字体颜色等
    那么我们前面写的代码中到底是哪里触发了重排和重绘呢?简单检查一下,不难发现两种方案都是在不停改变元素的width,元素的宽度一改变必然会引起重排和重绘,更何况是超频繁的改变呢!

    如何避免

    CSS

    • 避免使用table布局。
    • 尽可能在DOM树的最末端改变class。
    • 避免设置多层内联样式。
    • 将动画效果应用到position属性为absolute或fixed的元素上。
    • 避免使用CSS表达式(例如:calc())。

    JavaScript

    • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
    • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
    • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
    • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
    • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

    我们要尽可能的减少重排次数、重排范围,这样说很泛,下面是一些行之有效的建议

    样式集中改变

    // bad
    var left = 10;
    var top = 10;
    el.style.left = left + "px";
    el.style.top  = top  + "px";
    // good 
    el.className += " theclassname";
    // good
    el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
    

    缓存布局信息

    // bad 强制刷新 触发两次重排
    div.style.left = div.offsetLeft + 1 + 'px';
    div.style.top = div.offsetTop + 1 + 'px';
    
    // good 缓存布局信息 相当于读写分离
    var curLeft = div.offsetLeft;
    var curTop = div.offsetTop;
    div.style.left = curLeft + 1 + 'px';
    div.style.top = curTop + 1 + 'px';
    
    

    离线改变dom

    • 隐藏要操作的dom

    在要操作dom之前,通过display隐藏dom,当操作完成之后,才将元素的display属性为可见,因为不可见的元素不会触发重排和重绘。

    dom.display = 'none'
    // 修改dom样式
    dom.display = 'block'
    

    position属性为absolute或fixed

    position属性为absolute或fixed的元素,重排开销比较小,不用考虑它对其他元素的影响

  • 相关阅读:
    npm发包流程
    K8S 多集群管理命令行工具: KubeCM
    为什么打印出来的单词少一横,如H、A、e等等
    Redission锁的设计原理和应用
    ELK学习笔记
    题解 noip2018模拟测试赛(三十五)
    题解 noip2018模拟测试赛(三十三)
    题解 noip2018模拟测试赛(三十二)
    题解 noip2018模拟测试赛(三十一)
    题解 【BZOJ3309】DZY Loves Math
  • 原文地址:https://www.cnblogs.com/Hsong/p/14948770.html
Copyright © 2020-2023  润新知