• 详解重绘与回流


    详解重绘与回流

    0.12021.12.22 12:05:49字数 2,401阅读 3,516

    从输入url到看到页面,过程?

    1、输入url ( 协议、网络地址、资源路径 )

    2、查看浏览器缓存,看是否有缓存,如果有缓存,继续查看缓存是否过期,如果没有过期,直接返回缓存页面,如果没有缓存或者缓存过期,发送一个请求。

    3、浏览器解析url地址,获取协议、主机名、端口号和路径。

    4、获取主机ip地址过程

    (1)浏览器缓存

    (2)主机缓存

    (3)hosts文件

    (4)路由器缓存

    (5)DNS缓存

    (6)DNS递归查询

    5、浏览器发起和服务器的TCP连接,执行三次握手

    6、三次握手连接后,浏览器发送一个http请求

    7、服务器收到请求,转到相关的服务程序,期间可能需要连接并操作数据库(主要分get和post请求)

    8、服务器看是否需要缓存,服务器处理完请求,发出一个响应

    9、服务器并根据请求头包含信息决定是否需要关闭TCP连接(如需关闭,则需要四次挥手过程)

    10、浏览器对接收到的响应进行解码

    11、浏览器解析收到的响应并根据响应的内容(假如是HTML文件),启动渲染引擎开始解析HTML文档,并把标签转化成内容树中的DOM节点。同时它也开始解析样式数据,外链的css文件以及style标签内的样式。所有这些样式数据以及HTML中的可见性指令都是用来创建另一棵树——render 树

    12、处理嵌入的其他资源如css文件、js文件、图片文件、音视频等文件(处理过程类似上述)

    那么浏览器如何渲染页面呢?

    • 根据html文件构建DOM树和CSSOM树。构建DOM树期间,如果遇到JS,阻塞DOM树及CSSOM树的构建,优先加载JS文件,加载完毕,再继续构建DOM树及CSSOM树。

    • 构建渲染树(Render Tree)。

    • 页面的重绘(repaint)与重排(reflow,也有称回流)。页面渲染完成后,若JS操作了DOM节点,根据JS对DOM操作动作的大小,浏览器对页面进行重绘或是重排。

    一、构建DOM树及CSSOM树

    1.1 构建DOM树

    HTML 文档中的所有内容皆是节点,各节点之间拥有层级关系,如父子关系、兄弟关系等,彼此相连,构成DOM树。最常见的几种节点有:文档节点、元素节点、文本节点、属性节点、注释节点。

    DOM节点树中节点与HTML文档中内容一一对应,DOM树构建过程:读取html文档,将字节转换成字符,确定tokens(标签),再将tokens转换成节点,以节点构建 DOM 树。如图所示

    <html>
        <head>
            <meta charset="UTF-8">
            <link href=""/>
        </head>
        <body>
            <p>Hello, <span>web performance</span>students</p>
            <div><img src=""></div>
        </body>
    </html>
    
     
    img

    1.2 构建CSSOM树

    CSS文档中,所有元素皆是节点,与HTML文件中的标签节点一一对应。CSS中各节点之间同样拥有层级关系,如父子关系、兄弟关系等,彼此相连,构成CSSOM树。

    在构建DOM树的过程中,在 HTML 文档的 head 标签中遇到 link 标签,该标签引用了一个外部CSS样式表。由于预见到需要利用该CSS资源来渲染页面,浏览器会立即发出对该CSS资源的请求,并进行CSSDOM树的构建。

    CSSOM树构建过程与DOM树构建流程一致:读取CSS文档,将字节转换成字符,确定tokens(标签),再将tokens转换成节点,以节点构建 CSSOM 树。如图所示

    body{
    font-size: 16px;
    }
    p{
    font-weight: bold;
    }
    span{
    color: red;
    }
    p span{
    display: none ;
    }
    img{
    fLoat: right;
    }
    
     
    img

    1.3加载JS

    若在构建DOM树的过程中,当 HTML 解析器遇到一个 script 标记时,即遇到了js,将立即阻塞DOM树的构建,将控制权移交给 JavaScript 引擎,等到 JavaScript 引擎运行完毕,浏览器才会从中断的地方恢复DOM树的构建。 其根本原因在于,JS会对DOM节点进行操作,浏览器无法预测未来的DOM节点的具体内容,为了防止无效操作,节省资源,只能阻塞DOM树的构建。譬如,若不阻塞DOM树的构建,若JS删除了某个DOM节点A,那么浏览器为构建此节点A花费的资源就是无效的。

    若在HTML头部加载JS文件,由于JS阻塞,会推迟页面的首绘。为了加快页面渲染,一般将JS文件放到HTML底部进行加载,或是对JS文件执行async或defer加载。

    二.构建渲染树

    渲染树(Render Tree)由DOM树、CSSOM树合并而成,但并不是必须等DOM树及CSSOM树加载完成后才开始合并构建渲染树。三者的构建并无先后条件,亦非完全独立,而是会有交叉,并行构建。因此会形成一边加载,一边解析,一边渲染的工作现象。

    构建渲染树,根据渲染树计算每个可见元素的布局,并输出,将像素渲染到屏幕上

    三.页面的重绘(repaint)与回流(reflow)

    3.1 重绘(repaint):屏幕的一部分要重绘。渲染树节点发生改变,但不影响该节点在页面当中的空间位置及大小。譬如某个div标签节点的背景颜色、字体颜色等等发生改变,但是该div标签节点的宽、高、内外边距并不发生变化,此时触发浏览器重绘(repaint)。

    3.2 回流(reflow):也有称重排,当渲染树节点发生改变,影响了节点的几何属性(如宽、高、内边距、外边距、或是float、position、display:none;等等),导致节点位置发生变化,此时触发浏览器重排(reflow),需要重新生成渲染树。譬如JS为某个p标签节点添加新的样式:"display:none;"。导致该p标签被隐藏起来,该p标签之后的所有节点位置都会发生改变。此时浏览器需要重新生成渲染树,重新布局,即回流(reflow)。

    注意:回流必将引起重绘,而重绘不一定会引起回流。

    何时回引起回流?

    当页面布局和几何属性改变时就需要回流。下述情况会发生浏览器回流:

    1、添加或者删除可见的DOM元素;

    2、元素位置改变——display、float、position、overflow等等;

    3、元素尺寸改变——边距、填充、边框、宽度和高度

    4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

    5、页面渲染初始化;

    6、浏览器窗口尺寸改变——resize事件发生时;

    如何减少和避免回流,优化浏览器渲染过程?

    Reflow 的成本比 Repaint 的成本高得多的多。一个节点的 Reflow 很有可能导致子节点,甚至父节点以及兄弟节点的 Reflow 。(在一些高性能的电脑上也许还没什么,但是如果 Reflow 发生在手机上,那么这个过程是延慢加载和耗电的。)

    其实优化就是减少对render tree的操作,并减少对一些style信息的请求,尽量利用好浏览器的优化策略。

    1. 让操作元素离线处理
    • 使用documentFragment或div等元素进行缓存操作,先把所有要添加到元素添加到1个div,最后才把这个div append到body中。

    • 先display:none 隐藏元素,然后对该元素进行所有的操作,最后再显示该元素。因对display:none的元素进行操作不会引起回流、重绘。

    1. 将引起回流的属性赋值给变量,进行缓存,需要用到的时候直接使用变量就行。

    2. 减少操作影响的节点,影响的节点越多,则越消耗性能。

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

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

    4. 避免触发同步布局事件

      当我们访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。举个例子,比如说我们想将一个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';
        }
    }
    
     
     
    4人点赞
     
    前端
     
     
     
  • 相关阅读:
    ImportError: libXext.so.6: cannot open shared object file: No such file or directory
    Django项目添加日志
    Django项目DEBUG=False时配置静态文件
    Django项目DEBUG=False时配置静态文件
    真的佩服python强大表达力
    mycharm环境建立django项目并增删改查
    Apache配置https
    安卓签名
    Android studion不能启动问题
    带你入门函数式编程
  • 原文地址:https://www.cnblogs.com/sexintercourse/p/16538669.html
Copyright © 2020-2023  润新知