Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从浏览器关键渲染路径聊起 #61

Open
creeperyang opened this issue Mar 20, 2023 · 0 comments
Open

从浏览器关键渲染路径聊起 #61

creeperyang opened this issue Mar 20, 2023 · 0 comments

Comments

@creeperyang
Copy link
Owner

creeperyang commented Mar 20, 2023

(一)浏览器关键渲染路径-CRP

关键渲染路径是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。

我们这里只尽量精简描述这个渲染过程,更具体的可以去看MDN-关键渲染路径

image

在浏览器接收html时(浏览器收到数据的第一块时就可以开始解析收到的信息):

  1. 首先解析 HTML 并构建 DOM 树;

    1. 遇到非阻塞资源(如图片)会请求这些资源并且继续解析。
    2. 遇到阻塞资源如 <script> 标签(没有 async 或者 defer 属性的)会阻塞渲染并停止 HTML 的解析。
    3. 需要重点提CSS资源:CSS不会阻塞 HTML 的解析,但会阻塞 JavaScript(CSS后面的JavaScript需要等CSS下载完成),因为 JavaScript 经常用于查询元素的 CSS 属性。
      • JavaScript后面的CSS不会阻塞本JavaScript执行,并且本JavaScript查询的CSS属性也是不包含后面CSS内容的。
    4. 预加载扫描器帮助提前下载资源。
  2. 构建 CSSOM 树

  3. Style(也叫构建Render树)

    1. 渲染树包括了内容和样式:DOM 和 CSSOM 树结合为渲染树(或者叫样式树)。为了构造渲染树,浏览器检查每个节点,从 DOM 树的根节点开始,并且决定哪些 CSS 规则被添加。
  4. Layout(布局)

    1. Layout 是确定渲染树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程。
    2. Reflow 回流是对页面的任何部分或整个文档的任何后续大小和位置的确定。
    3. 第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。
  5. Paint

    1. 最后一步是将像素绘制在屏幕上。
    2. 还有一步叫 Compositing,即不同层的合成,但讨论CRP时,把它作为Paint的一部分即可。讨论Reflow/Repaint时可以单独拆出讨论。

CRP 流程整体比较简单直接,我们讨论其中比较有意思的几个细节:

1. script 的 defer VS async

具有 async 属性的脚本在完成下载后和 load 事件之前第一时间执行。

  • 不能保证多个async脚本的执行顺序(谁先加载完谁先执行);
  • 如果脚本下载完,解析器还在工作,意味着会中断DOM构建。
    image

具有 defer 属性的脚本在 HTML 解析完全完成之后执行,但在 DOMContentLoaded 事件之前执行。

  • 可以保证顺序;
  • 不会阻塞解析器。

image

如果在加载过程中更早地运行脚本很重要,请使用 async。

2. 怎么优化CRP?

  1. 优化网络和资源本身(不局限于CRP):

    • 利用CDN,利用HTTP2等,优化网络速度;
    • dns-prefetchpreconnect 等优化dns、tcp建立速度;
    • 资源压缩,tree-shaking 等清除无用代码等等,减小资源体积;
    • http缓存。
  2. 通过异步、延迟加载或者消除非关键资源来减少关键资源的请求数量,如defer/async/preload/prefetch

    • defer/async 来消除js对DOM构建的阻塞;
    • 通过代码分割,异步加载非首屏部分等等。
  3. 通过区分关键资源的优先级来优化被加载关键资源的顺序,来缩短关键路径长度。

    • preload/prefetch等优化资源加载顺序;
    • 资源放在合适位置:关键资源如CSS等放在 <head>;js放在<body>底部。

3. Reflow/Repaint 以及相关的优化

任何样式更新、DOM增删等等,造成布局(Layout)需要重新计算就称为回流(Reflow)。而不影响布局的节点样式/几何属性变更就是重绘(Repaint)。

回流严重影响性能,需要优化:

  1. 将频繁重绘或者回流的节点独立为新图层:will-change/translate3d/translatez 等等;
  2. 将频繁重绘或者回流的节点布局改为position:absolute/fixed
  3. visibility 替换 display: nonetransform 替代 top 等;
  4. 避免频繁操作DOM/更新样式,尽量批量读取/操作DOM样式。

(二)浏览器进程/线程角度看渲染流程以及 event loop

现代浏览器一般是多进程架构,以Chrome为例,一般会有以下进程:

  • 一个 Browser 进程(可以兼作 GPU 进程或者 Renderer 进程,即没有独立的 GPU 或者 Renderer 进程);
  • 一个 GPU 进程;
  • 多个 Renderer 进程,每个 Renderer 进程对应一个页面。

Renderer 进程:

  • Renderer 线程:运行Blink,称之为内核主线程,负责JS的解析执行,HTML/CSS解析,DOM操作,排版,图层树的构建和更新等任务;
  • Compositor 线程:运行Layer Compositor;
  • 其它线程,包括 worker 线程等等。

从架构上就可以看出,当 Renderer 线程被 JS 阻塞(忙于执行JS)时,卡顿掉帧是必然的。为了达到60FPS,必须限制JS运行时间、减少复杂的样式更新。

一桢内浏览器会做什么?

image

上面的图其实也某种程度就是浏览器的 event loop 流程

下面是几点需要注意的细节:

  1. JS 和 UI更新在同一个线程,所以JS长时间运行会阻塞UI;
  2. 当页面invisible时,帧数会主动降低(可能1000毫米以上调用一次timer);
  3. 善用 requestAnimationFrame。

(三)参考

  1. 关键渲染路径
  2. 渲染页面:浏览器的工作原理
  3. requestAnimationFrame Scheduling For Nerds
  4. 高效加载第三方 JavaScript
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant