• 探索浏览器对于HTML的渲染原理(过程)


    原文链接:https://github.com/FIGHTING-TOP/FE-knowlodge-base

    探索目的

    为了更好地优化我们前端页面的性能,特对基础原理进行考究

    大致过程

    从浏览器获取到HTML文件开始,浏览器会经历解析、渲染、交互三大阶段;

    解析

    浏览器会在收到HTML文件的第一次响应包后开始解析(即使该HTML大于14kb),解析过程包括DOM树和CSSOM树的构建、资源的预加载(通过预加载扫描器异步加载)、JavaScript 编译以及构建辅助功能树。DOM包含了页面的所有内容,CSSOM包含了页面的所有样式。

    渲染

    渲染过程包括Style、Layout、Paint以及还可能会有Compositing这些阶段,
    渲染器会在DOM树和CSSOM树构建好之后,将两棵树组合成一个render树,这个过程会计算所有可以显示标签的样式,可以显示的标签包括从body开始(包括body)没有display: none的所有节点,包含带有visibility: hidden的节点。
    render-tree-construction
    布局是浏览器从根节点开始遍历整棵render树,计算每个节点的尺寸和位置;第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。如果布局完成后有图片加载完成并且该图片没有指定大小,这样就会造成回流。
    绘制是将布局阶段生成的render树的多有节点转换成屏幕上的实际像素,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。

    在这个过程中,浏览器会将布局树中的元素分解为多个层,将内容提升到GPU上的层(脱离CPU上的主线程),从而提高绘制和重新绘制的性能。每一个带有一些特定的CSS属性的元素和一些特定标签元素都可以实例化一个层,像和元素,以及任何带有opacity``,3D转换will-changeCSS属性的元素都会和它们的子节点单独绘制一个层,当然,如果子节点满足以上条件则会再单独实例一个层。
    浏览器针对处理CSS动画和不会很好地触发重排(因此也导致重新绘制)的动画属性进行了优化。为了提高性能,可以将被动画化的节点从主线程移到GPU上。将导致合成的属性包括 3D transforms (transform: translateZ(), rotate3d(),etc.),animating transform 和 opacityposition: fixedwill-change,和 filter。一些元素,例如 <video><canvas> 和 <iframe>,也位于各自的图层上。 将元素提升为图层(也称为合成)时,动画转换属性将在GPU中完成,从而改善性能,尤其是在移动设备上。

    交互

    当我们看到页面显示出来后,整个页面的所有渲染工作可能并没有完成,因为这时页面可能还无法进行点击,滚动,触摸等操作,因为这个时候可能还有js没有执行完,也就是主线程仍在占用状态,特别是像绑定了window.onLoad这种的js逻辑。
    在测试页面性能的时候,有一项重要的指标就是TTI(Time to Interactive)是从第一个请求导致DNS查找和SSL连接到页面可交互时所用的时间。

    webkit 渲染流程图

    image

    gecko 渲染流程图

    image

    问题探究

    CSS的加载会阻塞页面的渲染吗?

    提出假设

    我们来分析一下,从上面的页面渲染流程来看,HTML的渲染过程是将解析阶段生成的DOM树和CSSOM树组合成一个render树,那么CSS的加载肯定是会阻塞CSSOM树的建立,那没有CSSOM树也就没办法合成render树,因此也就没办法渲染,所以CSS的加载是会阻塞页面的渲染的。

    验证假设

    我们来写段代码测试一下

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            console.log('走js这里了')
        </script>
        <link rel="stylesheet" href="https://www.google.com/123123.css">
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
    </html>

    执行结果
    test1

    从结果来看,当css文件请求没有结束之前页面是空白的,等css加载失败后页面才显示内容,这就说明我们的假设成立。
    在这个结果中,也可以看出css没有加载结束之前,js被执行了,那我们将js代码写在link标签的后面,他还会被执行吗?

    css加载会阻塞j后面的s运行吗?

    直接测试

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <link rel="stylesheet" href="https://www.google.com/123123.css">
        <script>
            console.log('走js这里了')
        </script>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
    </html>

    执行结果
    test2
    结果来看js是在css加载后执行了,则说明css的加载阻塞了后面js的运行。

    那么如果不是内嵌的js,使用src来引入一个js文件呢?

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="./test000.js"></script>
        <script>
            console.log('走link标签前面的内嵌js了')
        </script>
        <link rel="stylesheet" href="https://www.google.com/123123.css">
        <script src="./test111.js"></script>
        <script>
            console.log('走link后面的内嵌js了')
        </script>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
    </html>

    在test000.js中这样写

    console.log('This is test000.js')

    在test111.js中这样写

    console.log('This is test111.js')

    执行结果
    test3
    结果是一样的

    那如果使用了defer或者async属性呢?

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="./test000.js" defer></script>
        <script>
            console.log('走link标签前面的内嵌js了')
        </script>
        <link rel="stylesheet" href="https://www.google.com/123123.css">
        <script src="./test111.js" defer></script>
        <script>
            console.log('走link后面的内嵌js了')
        </script>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
    </html>

    结果是什么样呢?
    test4
    那要是把defer全部换成async呢?
    test5

    为什么会这样?
    因为defer和async都可以使浏览器异步加载并解析执行js文件,所以link标签不能阻塞js的文件的执行,你可能会说,那为什么defer被阻塞了呢?我们知道defer和async有点不一样的,在没有内嵌js时,defer修饰的js会按照DOM先后顺序依次执行,async则是先加载完成的先执行;在有内嵌js时,无论是defer还是async都会等待内嵌js执行完才会去执行它们,如果没有这两个属性就会按照DOM中的顺序依次执行各个js。

    在实际中,我们会使用一些方法来应对css加载阻塞js执行的问题,比如把js放在link之前然后使用DOMContentLoaded方法,或者之前在jQuery中常用的ready方法。

    Reference

  • 相关阅读:
    快速复习正则表达式
    常用正则表达式
    用SqlConnectionStringBuilder修改连接超时时间
    GRUB 启动 WIN PE 镜像(ISO)
    win xp 关闭动画屏幕角色,那只小狗
    win xp firefox,chrome 在浏览网页时字体发虚,可以设置为新宋体
    OpenFileDialog 打开快捷方式时,返回的是快捷方式引用的路径,而不是快捷方式(.lnk)自身的路径
    关闭IE 对剪切板访问的提示
    svn 提交代码 自动过滤技巧,自动过滤不想提交的文件和文件夹
    WinForm 打开文件夹
  • 原文地址:https://www.cnblogs.com/mingweiyard/p/14034463.html
Copyright © 2020-2023  润新知