浏览器的渲染过程

56 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情

浏览器作为前端程序员最紧密的工具,我们不仅要了解其通信原理,还要了解其渲染原理,这些也是面试中经常被问到的,你回答的熟练程度,也是考察一个前端程序员级别的重要指标之一,接下来咱们就来了解了解他~

浏览器渲染过程

JS 有引擎线程和渲染线程

JS 运行的时候可能会阻止 UI 渲染,这说明了两个线程是互斥的。这其中的原因是因为 JS 可以修改 DOM,如果在 JS 执行的时候 UI 线程还在工作,就可能导致不能安全的渲染 UI。这其实也是一个单线程的好处,得益于 JS 是单线程运行的,可以达到节省内存,节约上下文切换时间,没有锁的问题的好处。当然前面两点在服务端中更容易体现,对于锁的问题,形象的来说就是当我读取一个数字 15 的时候,同时有两个操作对数字进行了加减,这时候结果就出现了错误。解决这个问题也不难,只需要在读取的时候加锁,直到读取完毕之前都不能进行写入操作。

  • HTML 解析器,CSS 解析器, V8引擎, 三个东西, V8引擎和前两者是互斥的, 解析JS的时候会停止HTMLCSS 解析工作, 现在 在请求CSS 资源的时候,HTML解析器也是工作的, 谷歌浏览器内置了一个 CSS 解析器的定时, 也就是等待CSS解析比如 300ms, 等待事件结束以后,CSS如果还没有解析完, 就会直接执行HTML解析器, 所以将 CSS 写在上面
    1. 解析HTML,生成DOM树,解析CSS,生成CSSOM(CSS规则)树
      • 在构建 CSSOM 树时,会阻塞渲染,直至 CSSOM 树构建完成。并且构建 CSSOM 树是一个十分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS 选择器,执行速度越慢。
      • HTML 解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。并且 CSS 也会影响 JS 的执行,只有当解析完样式表才会执行 JS,所以也可以认为这种情况下,CSS 也会暂停构建 DOM
    2. DOM树和CSSOM树结合,生成渲染树(Render Tree)
    3. Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
    4. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素 浏览器怎么把 渲染树生成到 页面当中的? 调用了 系统给他的接口 API, 就像浏览器给JS 提供的V8引擎一样.
    5. Display:将像素发送给GPU,展示在页面上。

浏览器渲染1.png

  • 渲染树形成过程
    1. DOM树的根节点开始遍历每一个可见节点. 什么是不可见节点? <script>,<meta>,<head>,<link>,CSS隐藏了的节点: display: none, visibilityopacity隐藏的还是会展示在DOM树上
    2. 对于每一个节点,找到CSSOM树种对应样式,生成CSS渲染树

浏览器渲染2.png

图层

  • 可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用。
  • 生成新图层的方法:
    • 3D 变换:translate3dtranslateZ
    • will-change,video,ifram标签
    • 通过动画实现的opacity动画转换,position:fixed

回流

  • 浏览器从渲染树的根节点开始遍历,将可见DOM节点以及它对应的样式结合起来,形成渲染树, 一个页面每一次第一次打开 都会先执行一次回流
  • 什么时候会发生回流?
    1. 添加或删除DOM元素
    2. 修改元素位置或尺寸的变化
    3. 页面开始的渲染
    4. 浏览器窗口尺寸变化
    5. 改变字体,盒模型
  • 回流一定会触发重绘,重绘不一定会触发回流

重绘

  • 当节点需要更改外观,而不会影响布局的,比如改变color,透明度等. 重绘和回流实际上和event loop有关
  • 1.当 event loop 执行完 Microtasks (任务栈,包括宏任务,微任务)后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。 2.然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resizescroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。 3.判断是否触发了 media query 4.更新动画并且发送事件 5.判断是否有全屏操作事件 6.执行 requestAnimationFrame 回调 7.执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 8.更新界面 9.以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。

如何减少重绘和回流?

    1. 使用translate代替top
    2. 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
    3. 使用文档碎片进行节点操作,全部完成操作后再将节点碎片插入页面中--->createDocumentFragment()
    4. 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
    5. 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
    6. 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
    7. CSS 选择符从右往左匹配查找,避免 DOM 深度过深
    8. 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video 标签,浏览器会自动将该节点变为图层。