2023-06-30
浏览器
00
请注意,本文编写于 393 天前,最后修改于 150 天前,其中某些信息可能已经过时。

目录

常见面试题
导航过程
渲染流程
完整渲染流程
DOM解析
DOM树 如何生成
JS影响DOM解析吗?
CSS影响DOM解析吗?
显示器是如何显示图像的
双缓存
卡顿
Chrome中的合成技术
浏览器渲染一帧做了什么?
重排
重绘
直接合成
其他
性能优化
performance指标
网页核心性能指标
更多性能指标
与渲染相关的性能优化
GPU加速
will-change
css contain

本文以“当浏览器输入URL之后发生了什么”为主线系统阐述浏览器解析和渲染的过程,当然有一些知识点如DNS网络协议等,会提一嘴但不属于本文范畴不会深入讲解,此外本文还会介绍网页性能指标相关

常见面试题

  • 当浏览器输入URL发生了什么?
  • 说一说浏览器的渲染流程
  • 哪些属性会触发重排?
  • 为什么CSS动画比JavaScript动画高效?
  • 为什么有时候⽤translate来改变位置⽽不是定位?

image.png

导航过程

用户发出URL请求到页面开始解析的过程叫做导航

导航.drawio.png

红色为第一次请求(主线)

渲染流程

完整渲染流程

完成的渲染流程示意图

image.png

具体流程细节如下

渲染流程.drawio.png

大致总结:

  1. 渲染进程将HTML内容转换成DOM树结构
  2. 渲染引擎将CSS样式转换为styleSheets, 计算出dom节点样式
  3. 创建布局树,并计算元素的布局信息
  4. 对布局树进行分层,并生成分层树
  5. 为每个图层生成绘制列表并将其提交给合成线程
  6. 合成线程将图层分成图块,并在光珊化线程池中将图块转换成位图
  7. 合成线程发送绘制图块的命令DrawQuad给浏览器进程
  8. 浏览器进程根据DrawQuad消息生成页面并显示到显示器上

DOM解析

HTML 与DOM 的关系

  • HTML是死的, DOM是活的,渲染引擎将HTML转换成DOM之后才能理解内部结构

DOM的作用

  • 从页面的视角来看: DOM是生成页面的基础数据结构
  • 从JavaScript视角来看: JS可以对DOM结构进行访问,从而改变文档中的结构、样式和内容
  • 从安全视角来看:DOM是一道安全防线,一些不完全的内容在DOM解析阶段就被拒之门外了。

DOM树 如何生成

思考: HTML的解析是等待整个HTML加载完成之后开始解析,还是随着HTML文档边加载边解析?

答案 肯定是后者,因为效率高,具体流程如下

  • 网络进程收到响应头后,根据content-type判断文件的类型,如过是“text/html”在浏览器进程的协调下,网络进程和渲染进程之间会建立一个共享数据的管道。网络进程收到数据后就忘这个管道投放,渲染进程收到后会“喂”给HTML解析器
  • 网络传输过来的是字节流,字节流是如何转换成DOM的呢?
  • image.png
    • 第一个阶段,通过分词器将字节流转换为 Token
    • 第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。
  • HTML解析器通过栈结构来解析的

JS影响DOM解析吗?

因为JS文件的下载过程会阻塞DOM解析,而通常下载又是非常耗时的,会受到网络环境、JavaScript 文件大小等因素的影响。

不过 Chrome 浏览器做了很多优化,其中一个主要的优化是预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析 HTML 文件中包含的 JavaScript、CSS 等 相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。

开发中我们可以通过一些方案来规避

  1. 静态资源使用CDN加速
  2. 压缩JS文件体积
  3. 标记async或defer 或 type='module'

CSS影响DOM解析吗?

  • 一般情况下,浏览器遇到<link>标签会异步下载并继续解析HTML
  • 但是如果遇到JS操作DOM会暂停JS解析,等待CSS下载完成并应用才继续解析JS(强制布局),这时候CSS就间接影响DOM解析了

image.png

补充知识点:

  • 强制布局: 如果JS操作DOM 会暂停JS执行,等待渲染完成再执行JS
  • 布局抖动: 多次强制布局

显示器是如何显示图像的

双缓存

很多图形操作都很复杂需要大量的运算,比如一副完整的画面,可能需要很多次计算才能完成,如果每次计算完成一部分图像,就将其写入缓冲区,那么会造成一个后果就是现实一个稍微复杂的图像过程中,你看到的页面效果可能是一部分一部分地显示出来,因此刷新页面的过程中,让用户感觉到界面的闪烁

使用双缓存,可以让你将计算的中间结果放在另一个缓冲区,等全部计算技术,该缓存区已经存储了完整的图形之后,再将该缓冲区的图像数据一次性复制到缓冲区,这样整个图像的输出非常稳定。

虚拟DOM与真实DOM 真实DOM的问题:因为JS是单线程,DOM操作与JS执行互斥,如果JS对DOM的操作不当可能引发强制同步布局和布局抖动的问题,这些操作会大大降低渲染效率。 在这里你可以类比,把虚拟DOM堪称DOM的一个buffer,和图形显示一样,它会在完成一次完整的操作之后,再把结果应用到DOM上,这样就能减少一些不必要的更新,同时能保证DOM的稳定输出

卡顿

  1. 显示器一般固定频率每秒60次读取前缓冲区的图像
    1. 浏览器根据垂直同步信号来进行渲染, 这个时钟是16.67ms(1000/60=16.67)
    2. 60帧/s大约是人眼能识别的最小帧率,低于这个频率就会感觉卡顿
  2. 显卡负责合成新的图像,并将图像保存到后缓冲区中,一旦显卡把合成的图像写到后缓冲区,系统会将前后缓冲区内容互换。
    1. 通常显卡的更新频率和显示器的刷新频率一致
    2. 如果JS执行时间过长,就会发生丢帧(跳过当前渲染,继续执行JS)然后等待下一个垂直同步信号。

Chrome中的合成技术

chrome中的合成技术可以用三个词来概括总结:分层、分块和合成

为什么要引入分层合成机制?

  • 通常页面的渲染是非常复杂的,有的页面需要实现一些复杂的动画效果,比如点击菜单时弹出菜单动画效果,滚动鼠标滚轮时的动画效果,当然还有一些酷炫的3D动画效果。
  • 如果没有采用分层机制,从布局直接到生成目标图片的话,那么每次页面很小的变化时。都会触发重排或重绘机制,这种牵一发而动全身的绘制策略会严重影响页面的渲染效率。
  • 合成是在合成线程中完成的,不阻塞主线

分层合成机制的实现

  • 生成绘制列表(比如Paint BackGroundColor: Black | PaintCircle)
  • 光珊化:就是按照绘制列表生成图片,合成线程有了这些图片,会将这些图片合成一张图片,并最终生成图片发送到后缓冲区。

分块

  • 通常情况下页面的内容要比屏幕大得多,显示一个页面时,如果等待所有图层都生成完毕,再进行合成的话,会产生一些不必要的开销,这让合成图片的时间变得更久。
  • 所以合成线程将图层分割成大小固定的图块,优先绘制靠近适口的图块,这样就可以大大提高页面的显示速度。
  • 即便如此,也可能耗费不少时间,因为纹理上传(从计算机内存上传到GPU内存)的操作比较慢
  • 所以Chrome又有了一个策略:在首次合成图块的时候使用一个低分辨率的图片。比如可以时正常分辨率的一般,纹理就减少3/4

如何分层优化代码

  • 使用will-change告诉显然引擎你会对该元素做一些特效变换
  • will-change: transform;
  • 渲染引擎会讲该元素单独实现一帧,等待变换发生时,渲染引擎会通过合成线程去处理变换,不影响主线程的执行,这样大大提高了渲染效率。这也是CSS动画比JavaScript动画高效的原因(1)
  • 凡事都有两面性,渲染引擎为该元素准备一个独立的层,它占用的空间也会大大增加。

浏览器渲染一帧做了什么?

前面的渲染流程是指首屏渲染的过程,然而渲染渲染第二帧及后续帧则不需要那么麻烦。一般经历重排或重绘或合成三个过程之一即可

重排

如果改变了元素的几何属性,例如盒模型相关,浏览器会触发重新布局,解析之后的一些列子阶段。也叫回流

image.png

触发页面重布局的属性

  • 盒子模型相关属性会触发重布局
    • width height padding margin border-width border min-height
    • display
  • 定位属性及浮动也会触发重布局
    • top bottom left right position float clear
  • 改变节点内部文字结构也会触发重布局
    • text-align font-weight overflow font-family line-height vertival-align white-space font-size

重绘

render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

相对于重排:重绘省去了布局和分层阶段,所以执行效率要比重排操作要高一些

image.png

只触发重绘的属性

  • color border-style border-radius text-decoration visibility
  • background background-image background-position background-repeat background-size
  • outline-color outline outline-style outline-width box-shadow

直接合成

image.png

假如我们使用了CSS的transform来实现动画效果,这可以避开重排和重绘,合成能大大提升绘制效率

  • 因合成再非主线程运行,这个也是为什么CSS动画性能比JS动画性能好的原因(2)

其他

完整的一帧 image.png

  • requestAnimationFrame 在渲染一帧前如果有空闲时间则执行它
  • requestIdelCallback 在每一帧渲染后,下一帧绘制之前如果有空闲时间,则调用之

像素管道 image.png

性能优化

performance指标

window.performance.timing

  1. navigationStart 触发跳转的时间,如果没有前一个页面则是从fetchStart开始
  2. unloadEventStart / unloadEventEnd 前一个页面卸载的时间戳,无上一个页面,默认0
  3. redirectStart / redirectEnd 如果没有重定向两个值都是0
  4. worker 初始化事件 + 加载事件的时间
  5. fetchStart 页面开始的事件
  6. DomainLookupStart / DomainLookupEnd DNS 递归解析 迭代查询的时间, 这里会拿到 domain 对应的 ip
  7. connectStart / connectEnd 等待TCP队列及三次握手时间 a. chrome有个机制,同一个域名,同时最多只能建立6个TCP连接,剩下的请求会进入排队等待状态
  8. secureConnectionStart 建立https连接的时间
  9. requestStart / responseEnd 请求时间
  10. domLoading 开始加载DOM
  11. domInteractive 只是完成dom的解析,并没有开始加载网络资源
  12. domContentLoadedEventStart / domContentLoadedEventEnd 开始解析DOM树事件 readyStateChange
  13. domComplete DOM树ready
  14. loadEventStart / loadEventEnd 执行脚本开始与结束

网页核心性能指标

上述指标到 domComplete只是index.html加载完成的首次渲染,对于SPA应用来说还基本处于白屏状态,衡量网站性能还需要更多指标

Core Web Vitals(网页核心性能指标)是 Google(谷歌)认为在网页的整体用户体验中很重要的一组特定因素。它是由三个特定的页面速度和用户交互测量值组成:链路控制协议、网页交互性和可视元素偏移。

LCP(Largest Contentfull Paint)最大内容渲染时间 衡量页面装载的性能,页面加载前2.5s内必须要进行最大内容渲染 最大内容包括

  • <image> <svg> <video>
  • 通过url加载内容的模块
  • 主要文本模块以及起内联模块

LCP值低的原因

  • 资源慢 包含静态资源和动态资源 静态资源可以走缓存+cdn
  • 渲染逻辑 简化DOM结构与逻辑

FID(First Input Delay) 衡量交互体验 页面首次输入延迟小于100ms

  • 优化DOM渲染
  • 优化算法 JS逻辑 (时间与空间的权衡)
  • 预获取 预加载 懒加载 首屏服务端渲染
  • 解决长任务问题阻塞渲染50ms以上,尽量拆分做交互弥补, 再比如react时间切片的思想
  • 使用 JS workers 缓解DOM渲染与JS执行阻塞问题
    • Web worker / Service worker / worklet
javascript
// main.js const worker = new Worker('./worker.js'); worker.postMessage(' Come on & work~'); worker.onmessage = function(e) { console.log(e.data); } // worker.js self.onmessage = function(e) { console.log(e.data); self.postMessage('干完了,下班了') }
javascript
// main.js navigator.serviceWorker.register('service-worker.js'); // service-worker.js self.addEventListener('install', fn) self.addEventListener('active', fn) self.addEventListener('fetch', (e) => { e.respondwith(catchs.match(event.request)) })
javascript
// worklet 俗称 JS in CSS /* main.js */ CSS.paintWorklet.addModule('worklet.js'); /* worklet.js */ registerPaint('myGradient', class { paint(ctx, size, prop) { var gradient = ctx.createLinearGradient(0, 0, 0, size.height - 5); gradient.addColorStop(0, "black"); gradient.addColorStop(1, "white"); ctx.fillStyle = gradient; ctx.fillRect(0, 0, size.width, size.height); } }) /* app */ .content { background-image: paint(myGradient); }

CLS (Cumulative Layout Shift)

  • 累计布局偏移 - 衡量视觉稳定性

  • 页面要保持CLS小于0.1 => 可见元素从前一帧到后一帧改变位置的动作

  • 避免使用无尺寸元素

html
<img srcset="a-320w.jpg 320w,a-480w.jpg 480w, a-800w.jpg 800w," sizes="(max-width: 320px) 300px, (max-width: 480px) 440px, 800px">
  • 减少内部内容的插入 => 会影响整体的布局
  • 字体控制

chrome应用商店有个CWV工具 Core Web Vitals Annotations

chrome devtools performance

  • FPS、CPU、网络请求
  • 网络任务队列
  • JS消耗时间、性能占用
  • 浏览器绘制页面的帧布局

更多性能指标

image.png

  • FP(First Paint),表示渲染出第一个像素点。FP一般在HTML解析完成或者解析一部分时候触发。
  • FCP(First Contentful Paint),表示渲染出第一个内容,这里的“内容”可以是文本、图片、canvas。
  • FMP(First Meaningful Paint),首次渲染有意义的内容的时间,“有意义”没有一个标准的定义,FMP的计算方法也很复杂。
  • DCL(DOMContentLoaded),DOM解析完毕。
  • L (onLoad Event) onLoad事件触发
  • SI(Speed Index)速度指数
  • **FSP **(First Screen Paint)首屏时间
  • TTI(Time to Interactive)首次可交互时间
  • TTFB (Time to First Byte)接收首子节时间
  • LCP(largest contentful Paint),最大内容渲染时间。
  • TBT(Total Blocking Time)阻塞总时间

js可以通过PerformanceObserver 获取这些指标

js
// 创建一个新的 PerformanceObserver 实例 let observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { // 处理每个性能条目 console.log(entry); } }); // 开始观察特定类型的性能条目 observer.observe({ entryTypes: ['paint', 'mark', 'measure'] }); // 在不需要时,停止观察 // observer.disconnect();

衡量性能的工具

  • Chrome Devtools performance
  • Lighthouse
  • WebPageTest
  • web-vitals

与渲染相关的性能优化

GPU加速

  • CPU位于计算机的主板上,它被称为计算机的大脑
  • GPU位于计算机的显卡上,专门负责处理和渲染图像,所以它的效率更高

硬件加速又称GPU加速, 以来浏览器渲染时使用的分层模型

  • CSS的animationtransformtransition不会自动进行GPU加速
  • CSS有一些为数不多的属性如opacity translateZ() translate3d()会开启硬件加速

注意 GPU加速也有缺点

  1. 创建新图层,会占用更多的内存,尤其在移动端,移动端设备内存有限
  2. 耗电

will-change

  • will-change属性允许你提前告知浏览器你可能会对一个元素进行什么样的改变,这样浏览器就可以提前设置适当的优化
  • 使用 will-change 属性并不会立即触发元素的重绘或重排
  • 浏览器会对相应的元素做出优化,例如为其创建新的图层,以便在动画期间进行硬件加速,从而提高性能。

will-change 属性有以下几种使用方式:

指定要改变的属性:will-change: property;,其中 property 是要改变的 CSS 属性的名称,比如 transformopacityscroll-position 等。

指定多个属性:will-change: property1, property2, ...;,可以同时指定多个属性,以逗号分隔。

全局优化提示:will-change: auto;,表示元素可能会有任何属性的改变。

注意事项

  • will-change 不要滥用,浏览器进行优化准备也有成本
  • 在一个元素发生变化之前立即对其设置will-change,几乎没有任何效果
  • 元素变化完成且后续不在需要 要及时的移除will-change

本段落参考资料

css contain

  • 浏览器已经尽可能的在页面下做了最大的优化,但每个浏览器引擎的实现方法并不尽相同。
  • 而 contain 属性可以提供一种标准的方式让开发人员告诉 浏览器 某些方面可以这样优化,哪些不能优化。
  • contain保证了它和它的子元素的DOM变化不会触发父元素的重新布局、渲染等。

contain 属性值有7中

  • none
  • layout 告知浏览器此元素及子元素不会在影响外部元素的布局
  • style 官方说辞是因为存在某些风险,暂时被移除,可能在规范的第二版会重新定义吧
  • paint 元素的子元素不会在此元素的边界之外被展示
  • size 元素的渲染不会受到其子元素内容的影响。
  • content 相当于 contain: size layout paint
  • strict 相当于 contain: layout paint

本段参考资料

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!