浏览器的底层渲染机制


前言

CRP:critical rendering path 关键渲染路径围绕渲染的机制和步骤,去详细的进行每一步的优化,以此来提高页面的渲速度和运行性能


一、从服务器基于HTTP网路请求回来的数据

从服务器请求回来的是16进制的文件流,
浏览器会把他解析为字符串(html字符串)
按照w3c规则识别成一个个的节点【词法解析】
最后生成树

二、进程和线程

访问页面,首先请求回来的是一个HTML文档,浏览器开始自上而下渲染

  • 进程:一般指一个程序(浏览器打开一个页面,就相当于开了一个进程)
  • 线程:进程中具体去执行事务的东西,一个线程同时只能干一件事情
    一个进程中,可能会包含一到多个线程

同步编程:一般是只有一个线程去处理事情,上面的事情处理不完,下面的事情无法处理「一件事一件事去干」
异步编程:

  • 多线程异步编程
  • 单线程异步编程(JS是EventQueue+EventLoop机制完成单线程异步编程的)

浏览器是可以开辟多个进程/线程的

    • GUI渲染线程:渲染页面
    • JS引擎线程:渲染JS代码的
    • HTTP网络线程,可以开辟N多个:从服务器获取资源和数据的
    • 定时器监听线程
    • DOM监听线程

三、渲染页面过程

3-1、遇到style内嵌样式,GUI直接渲染即可

如果CSS代码量比较少,我么直接内嵌即可,拉去HTML的时候,同时CSS也回来了,渲染的时候直接就渲染了
但是如果CSS代码比较多,如果还用内嵌,一方面会影响HTML的拉取速度,也不利于代码的维护,此时还是用外链的方式比较好
在这里插入图片描述

3-2、外链式和导入式

遇到link,浏览器开辟一个HTTP线程去请求资源文件信息,同时GUI继续向下渲染「异步」

  • 浏览器同时能够发送的HTTP请求是有数量限制的(谷歌:5~7个)
  • 超过最大并发限制的HTTP请求需要排队等待
  • HTTP请求一定是越少越好…

遇到@import,浏览器也是开辟HTTP线程去请求资源,但是此时GUI也暂定了(导入式样式会阻碍GUI的渲染),当资源请求回来之后,GUI才能继续渲染「同步」

  • 真实项目中应该避免使用@import

3-3.遇到 <script 标签,会阻碍GUI的渲染

假如有一个 js文件。里面获取一个dom

  let box = document.getElementById('box');
    console.log(box);

在html文件里面 直接这样引用
在这里插入图片描述
控制台里面会打印null
这就是被阻碍了gui渲染

解决这种问题用以下几种方法解决

window.addEventListener('load', function () {
    // 等待页面所有资源都加载完,才会触发执行
    let box = document.getElementById('box');
    console.log(box);
});
window.addEventListener('DOMContentLoaded', function () {
    // 只要等待DOM树渲染完即可,优先load触发 ->
    //   $(function(){})
    //   $(document).ready(function(){})
    let box = document.getElementById('box');
    console.log(box);
});

或者在script里面加 async defer
在这里插入图片描述

3-4、async defer区别

遇到

  • defer:和link是类似的机制了,不会阻碍GUI渲染,当GUI渲染完,才会把请求回来的JS去渲染…
  • async:请求JS资源是异步的「单独开辟HTTP去请求」,此时GUI继续渲染;但是一但当JS请求回来,会立即暂停GUI的处理,接下来去渲染JS…

假入我们有5个JS的请求,如果不设置任何属性,肯定是按照顺序请求和渲染JS的「依赖关系是有效的」;但是如果设置async,谁先请求回来就先渲染谁,依赖关系是无效的;如果使用defer是可以建立依赖关系的(浏览器内部在GUI渲染完成后,等待所有设置defer的资源都请求回来,再按照编写的依赖顺序去加载渲染js);

真实项目开发,我们一般把link放在页面的头部「是为了在没有渲染DOM的时候,就通知HTTP去请求CSS了,这样DOM渲染玩,CSS也差不多回来了,更有效的利用时间,提高页面的渲染速度」
我们一般把JS放在页面的底部,防止其阻碍GUI的渲染,如果不放在底部,我们最好设置上async/defer…

在这里插入图片描述


四、总结渲染步骤

  1. 处理 HTML 标记,构建 DOM 树
  2. 处理 CSS 标记,构建 CSSOM 树
  3. 将 DOM 树和 CSSOM 树融合成渲染树
  4. 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流 => 布局(Layout)或
    重排(reflow)
  5. 根据渲染树以及回流得到的几何信息,得到节点的绝对像素 => 绘制(painting)

DOM TREE(DOMContentLoaded事件触发) -> 「执行JS」? -> CSSOM TREE -> RENDER
TREE渲染树「浏览器未来是按照这个树来绘制页面的」-> Layout布局计算「回流/重排」-> Painting绘制「重绘」{ 分层绘制
}
页面第一次渲染,必然会引发一次回流和重绘 + 如果我们改变了元素的位置和大小,浏览器需要重新计算元素在视口中的位置和大小信息,重新计算的过程是回流/重排,一但发生了回流操作,一定也会触发重绘「很消耗性能:DOM操作消耗性能,90%说的都是它」
但是如果只是一些普通样式的改变,位置和大小不变,只需要重绘即可

五、重排和重绘,如何避免页面的重排重绘

在这里插入图片描述
比如:一开始有一个50*50px的box 之后通过后面的操作将盒子大小重新设置了,这样必然引起页面的重排重绘。

#box {
            background: lightcoral;
            width: 50px;
            height: 50px;
            display: flex;
        }
let box = document.querySelector('#box');
    box.style.width = '100px';
    box.style.height = '100px';

按老版本的计算会引起两次重拍重绘,可以通过集中改变样式的方法

1、集中改变样式

  // 集中改变样式
    box.style.cssText = "width:100px;height:100px;";
    box.className = 'big'; 

但是新版浏览器不会

// 新版浏览器都有一个机制:渲染队列机制
    let box = document.querySelector('#box');
    box.style.width = '100px';
    console.log(box.style.width); //获取样式:style.xxx/getComputedStyle/getBoundingClientRect/clientWidth.../offsetWidth.../scrollWidth...  刷新浏览器渲染队列
    box.style.height = '100px';

在这里插入图片描述

2、读写分离:把获取样式和设置样式的操作分离开

    box.style.width = '100px';
    box.style.height = '100px';
    console.log(box.style.width);

    // ---读写分离
    let prevW = parseFloat(window.getComputedStyle(box)['width']),
        prevH = parseFloat(window.getComputedStyle(box)['height']);
    box.style.width = prevW + 100 + 'px';
    box.style.height = prevH + 100 + 'px';

可以利用读写分离的方法了来做优化,但是有些时候需要特意用到读写不分离的操作
比如使用css3写一个轮播图

<style>
      *{
          margin: 0px;
          padding: 0px;
      }
      .container{
        position: relative;
        width: 800px;
        height: 300px;
        margin: 20px auto;
        overflow: hidden;
      }
      .wrapper{
        position: absolute;
        left: 0;
        top: 0;
        width: 4000px;
        height: 100%;
        display: flex;
        justify-items: flex-start;
        align-items: center;

        /* 动画 */
        transition: left .3s linear 0s;
      }
      .slide{
          width: 800px;
          height: 100%;
      }
      .slide img{
          width: 100%;
          height: 100%;
      }
    </style>
<body>
    <div class="container">
        <div class="wrapper">
            <div class="slide">
                <img src="images/1.jpg" alt="">
            </div>
            <div class="slide">
                <img src="images/2.webp" alt="">
            </div>
            <div class="slide">
                <img src="images/3.webp" alt="">
            </div>
            <div class="slide">
                <img src="images/4.webp" alt="">
            </div>
            <!-- 克隆 -->
            <div class="slide">
                <img src="images/1.jpg" alt="">
            </div>
        </div>
    </div>
</body>
 // 思路:轮播到最后一张 再要运动 我们先让其立即蹦到真实的第一张,紧接着蹦到第二张
    var container = document.querySelector('.container'),
        wrapper = container.querySelector('.wrapper'),
        step = 0,
        timer;
        
        timer = setInterval(function(){
            step ++
            if(step >=5){
                wrapper.style.transition = 'left 0s'
                wrapper.style.left = 0;
                //运动到第二张
                step =1
                //刷新队列列表
                wrapper.offsetLeft
            }
            wrapper.style.transition = 'left .3s'
            wrapper.style.left = `-${step * 800}px`
        },1000)

在这里插入图片描述
这一句可以使得页面立即重排重绘,使得轮播流畅
在这里插入图片描述

在这里插入图片描述
文档碎片的应用解决元素的批量修改

let box = document.querySelector('#box'),
        frag = document.createDocumentFragment();
    for (let i = 0; i < 10; i++) {
        let span = document.createElement('span');
        span.innerHTML = i + 1;
        frag.appendChild(span);
    }
    box.appendChild(frag);

或者以字符串的拼接

let box = document.querySelector('#box'),
        str = ``;
    for (let i = 0; i < 10; i++) {
        str += `<span>${i+1}</span>`;
    }
    box.innerHTML = str;

在这里插入图片描述
尽量少去改动left。可以使用translate()。等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值