从url到页面渲染的前世今生(今生篇)

743 阅读3分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

当有人问浏览器输入url到页面渲染发生了什么,你会怎么回答? 下面就来聊聊浏览器输入url到页面渲染发生了什么,浏览器将前端的代码请求过来了(这是前世),那么拿到代码浏览器如何绘制我们就叫今生。

那么浏览器是如何渲染页面的呢?一共分为下面五个步骤。

1.将HTML解析成DOM树(其实就是一个js对象)

当浏览器拿到这个html的时候,不会立即将这个html绘制到页面上,而是先生成一个DOM树

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div class="wrap">
        <p>hello</p>
    </div>
    <script>
    // 生成的DOM树
        var dom = {
            target: {
                el: 'div',
                class: 'wrap'children: [
                    {
                        el: 'p',
                        class: '',
                        value: ''
                    }
                ]
            }
        }
    </script>
</body>

</html>

2. CSS代码解析生成CSSOM树

DOM树生成完成之后就会将CSS代码解析生成CSSOM树,也就是类似生成DOM树。

3. 结合DOM树和CSSOM树,生成一棵 render树

第三步就是将前面生成的DOM树和CSSOM树融合成一棵 render树

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .wrap {
            width: 100%;
            height: 100%;
            background: #000;
        }
    </style>
</head>

<body>
    <!-- 当浏览器获取到这个html的时候,不会立即将这个html绘制到页面上,而是先生成一个DOM树 -->
    <div class="wrap">
        <p>hello</p>
    </div>

    <script>
        var dom = {
            target: {
                el: 'div',
                class: 'wrap',
                // 将CSSOM树融合进来
                style: {
                width: '100%',
                height: '100%',
                 .... // 其他的一些样式
                },
                children: [
                    {
                        el: 'p',
                        class: '',
                        value: ''
                    }
                ]
            }
        }
    </script>
</body>

</html>

4. 布局,将渲染树的节点进行平面合成

这一步只在浏览器里面执行,并没有渲染到页面上,可以理解为浏览器将完整页面的蓝图合成了,浏览器的大脑已经知道了即将要渲染的页面的样子

5. 绘制页面到屏幕上 (render-UI)

这一步我们才能看到页面,这是一个异步操作。

渲染

当我们访问某个页面的时候,导致页面内容变化就会导致页面重新渲染;当重新渲染的时候就不再需要重新生成DOM树、CSSOM树 和render树,而只要重新执行第四第五步。

那么就引出来第四步的重排(重新排版)与第五步的重绘(重新绘制)

重排

发生重排一定会发生重绘, 会发生重排的操作:

  1. window大小被更改
  2. 增加或删除DOM解构结构
  3. 元素尺寸变化
  4. offsetWidth 和 offsetHeight , offset... , clientWidth,client...,scrollTop,scroll... 所有导致元素几何信息发生变化的操作

重绘 第五步

所有导致元素非几何信息发生变化的操作 而boder-radious影响的是重绘而不是重排,因为即使将某个长方形的四个角变成圆形的,但是本身长方形这个容器没有改变,只是让这四个角看不见了

下面有个问题执行下面的代码,浏览器发生了几次重排,几次重绘

<body>
   <div id="app"></div>
   <script>
       let el = document.getElementById('app')
       el.style.width = (el.offsetWidth + 1) + 'px'
       el.style.width = 1 + 'px'

   </script>

</body>

那么肯定会有人说浏览器发生了三次重排,三次重绘。其实不然,我们要先知道浏览器的优化策略:当改变元素的几何信息导致重排发生,浏览器提供一个渲染队列用于临时存储该次重排,浏览器继续执行代码。如果还有几何信息修改,继续入队,直到没有样式修改。然后浏览器会按照渲染队列来批量优化重排过程

所以在执行上面的代码的时候,重排这件事情浏览器会一次性的执行,因为每次浏览器识别到offsetWidth时就会重排,然后浏览器就会将这次重排挂到渲染队列,最后再一次执行。所以浏览器发生了一次重排,一次重绘。

那么肯定有人会问下面的代码会发生几次?

div.style.left='10px'
console.log(div.offsetLeft)
div.style.top='10px'
console.log(div.offsetTop)

肯定有人说是两次,也肯定会有人说一次的,不纠结;当浏览器看到offsetLeft时,浏览器就会强制将渲染队列清空然后再重排,所以会执行两次重排,两次重绘