Skip to content

浏览器渲染

浏览器渲染引擎

  • Webkit: Chrome Safari
  • Gecko: firefox

加载页面和渲染过程

tips 加载过程和渲染过程分开说。简明扼要、抓住要点,不拖沓

浏览器从加载到渲染页面的过程

加载过程: 要点

  1. 浏览器根据DNS服务器得到域名的IP地址
  2. 向这个IP的机器发送HTTP请求
  3. 服务器收到、处理并返回HTTP请求
  4. 浏览器得到返回结果(html)

渲染过程

  1. 根据HTML结构生成DOM树
  2. 根据CSS生成CSSOM树
  3. 将DOM和CSSOM整合形成RenderTree
  4. 根据RenderTree开始渲染和展示
  5. 遇到script时会执行并阻塞渲染

link、img、script的下载是异步的,而执行是阻塞的。

为何要将CSS放在HTML头部?

这样会让浏览器尽早拿到CSS尽早生成CSSOM,然后在解析HTML后之后一次性生成最终的RenderTree,渲染一次即可。如果CSS放在HTML底部,会出现渲染卡顿的情况,影响性能和体验。

为何要将JS放在HTML底部?

JS放在底部可以保证让浏览器优先渲染完现有的HTML内容,让用户先看到内容,体验好。另外,JS执行如果涉及DOM操作,得等待DOM解析完成之后

参考推荐 《从输入 URL 到页面加载完成的过程中都发生了什么事情》


渲染过程

浏览器接收到 HTML 文件并转换为 DOM 树

  1. 网络传输中的字节数据转为字符串
  2. 字符串通过词法分析 转换为 标记token - 标记化
  3. 标记转为Node 根据 Node构建为 DOM树

字节数据 => 字符串 => Token => Node => DOM

将CSS文件转换为CSSOM树

字节数据 => 字符串 => Token => Node => CSSOM

层级扁平, 从右往左, 多用className

生成渲染树

渲染树只包含要显示的节点及其样式信息,若 某节点为 display: none 是不会在渲染树中显示的。

根据渲染树进行 布局Layout(回流),然后调用GPU绘制,合并图层,最终显示在屏幕上。


涉及知识点及思考

为什么DOM操作很慢

  1. DOM操作是渲染引擎,而JS运行则是在JS引擎中,线程间的通信,有性能损耗
  2. DOM节点 上属性 方法
  3. 操作DOM可能有回流、重绘发生,也会有性能问题

插入 n+ 个DOM,如何让页面不卡顿

  1. requestAnimationFrame
  2. 虚拟滚动 virtualized scroller 原理:只渲染可视区内容,非可见区域完全不渲染,当滚动时实时去替换渲染的内容

什么情况下阻塞渲染

  1. 渲染前要有渲染树,所以尽快生成 render Tree,降低需要渲染的文件大小(SSR),扁平层级,优化选择器

  2. 解析script会阻塞,所以script放body后,或者添加 defer或async属性

回流和重绘

  • 重绘:外观改变而不需要影响布局的,color background
  • 回流:几何属性或布局属性改变,需要进行计算称为回流

性能问题:

  • 改变window大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型

回流和重绘和浏览器的EventLoop也有关:

  1. 当EventLoop执行完Microtasks,会判断document是否需要更新,因为浏览器是60hz刷新率 每16.67ms更新一次
  2. 然后判断是否有resize和scroll事件(resize scroll自带16.67ms的节流)
  3. 判断是否触发了media query
  4. 更新动画并且发送事件
  5. 判断是否有全屏操作事件
  6. 执行requestAnimationFrame回调
  7. 执行IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载,但兼容性不好
  8. 更新界面
  9. 一帧中可能会做的事,若有空闲,还会去执行 requestIdleCallback回调

https://link.juejin.im/?target=https%3A%2F%2Fhtml.spec.whatwg.org%2Fmultipage%2Fwebappapis.html%23event-loop-processing-model


减少回流重绘

节流

js
function throttle(fn, delay = 500) {
  let lastTime = 0
  return function(...args) {
    let now = +new Date()
    if (now - lastTime > delay) {
      lastTime = now
      fn.apply(this, args)
    }
  }
}
setInterval(throttle(
  () => console.log(1)
), 1)

防抖

js
function debounce(fn, delay = 500) {
  let timer = null
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

在不考虑缓存和网络协议前提下,考虑通过哪些方式来最快渲染页面

  1. 文件大小考虑
  2. script标签的使用上考虑
  3. 从CSS、HTML书写上考虑
  4. 尽可能减少渲染页 所需要加载的资源

预加载

在开发中可能遇到,有资源不需要马上用到,但希望尽早获取,就可以用预加载

预加载是声明式的fetch,强制浏览器请求资源,且不会阻塞onload事件,可使用以下代码开启预加载

<link rel="preload" href="http://example.com">

预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。

预渲染

可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染

<link rel="prerender" href="http://example.com">

预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是白白浪费资源去渲染。

懒执行

将某些逻辑延迟到使用时再计算。可用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒

懒加载

将不关键的资源延后加载

图片,进入或即将进入可视区再请求加载需要的资源。

CDN

静态资源尽量使用CDN加载,由于浏览器对于某个域名有并发请求上限,可考虑使用多个CDN域名。

加载静态资源需要注意CDN域名要与主站不同,否则每次请求都会带上主站的Cookie,平白消耗流量。

共 20 个模块,1301 篇 Markdown 文档。