浏览器详解(四) 渲染

大家好,我是半虹,这篇文章来讲浏览器渲染


1、基本介绍

浏览器是多进程多线程的架构,包括有浏览器进程、渲染器进程、GPU 进程、插件进程等

上篇文章中我们介绍过浏览器进程,作为浏览器主进程,负责浏览器基本界面的交互和底层功能的执行

在这篇文章中我们来介绍渲染器进程,这是浏览器的内核,也称为渲染进程


渲染进程负责标签页内所发生的事情,主要包括页面渲染、脚本执行、事件处理等

其中包含多个重要的线程以协同工作:

  1. GUI 渲染线程:负责页面的渲染,解析 HTML、CSS,构建 DOM、CSSOM,布局和绘制等

            如果页面要回流或重绘,也由该线程去执行

  2. JS  引擎线程:负责解析和执行 JavaScript 脚本,注意  JS 引擎线程与 GUI 渲染线程互斥

  3. 事件触发线程: 控制事件循环,并管理事件队列

            若当 JS 引擎执行触发异步事件,会将对应的任务交给相应的线程执行处理

            等到任务完成之后,把任务对应的回调添加到事件队列,等待 JS 引擎处理

  4. 定时触发线程: 负责处理特定任务,具体来说就是用于 setTimeoutsetInterval 计时

            等到计时结束,通知事件触发线程,由事件触发线程将对应的回调加入队列

  5. 异步请求线程: 负责处理特定任务,具体来说就是处理 AJAX 请求

            等到请求完成,通知事件触发线程,由事件触发线程将对应的回调加入队列


下面我们通过例子,异步请求和定时操作,来说明线程之间如何协同工作

线程协作

上述多个线程大体上可以分成两组

  1. 渲染引擎:其中核心为 GUI 渲染线程
  2. 脚本引擎:其中核心为  JS  引擎线程,辅助为事件触发线程、定时触发线程、异步请求线程

目前市面主流浏览器使用的渲染引擎和脚本引擎总结如下:

浏览器渲染引擎脚本引擎
SafariWebkit / WebCoreWebkit / JavaScriptCore
ChromeBlinkV8
FirefoxGeckoSpiderMonkeyTraceMonkeyJaegerMonkey
IETridentChakra
EdgeEdgeHTMLChakra

在这篇文章中我们主要介绍渲染过程,至于 JS 的解析过程,以后有空的话会专门再讲

好了,下面来正式开始介绍浏览器中的渲染过程


2、渲染流程

渲染引擎渲染页面的过程,也称为关键渲染路径,具体步骤如下:

  1. 解析 HTML 构建 DOM

    具体步骤如下:

    转换:从网络或磁盘读取 HTML 文件原始字节,根据文件编码将字节转换为字符串

    分词:根据 HTML 规范,将上述字符串切分为不同的标记,如 <html><body>

    语法 分析:将上述标记转换为对象,对象中包含语法信息,如属性、属性值等信息

    DOM 构建:将上述对象链接在树状的结构中以标识父子和兄弟关系,这个树状结构就是 DOM


    需要注意的是:

    在读取 HTML 时,DOM 的构建已经开始,可以将上面的步骤理解成是流水线并行

    上一个步骤每完成一点,就会将结果传递给下一个步骤,不会等一个步骤完全执行完才进行下一个步骤


    关于脚本资源

    当 HTML 解析器遇到 <script> 标签时,会暂停 HTML 的解析,并开始加载、解析和执行 JavaScript

    这是因为  JavaScript 可能会修改 DOM 的结构,若明确  JavaScript  不会修改 DOM, 那么:

    你可以为  <script> 标签加 async  属性,这样是异步加载 JavaScript,但是加载完马上就会开始执行

    也可以为  <script> 标签加 defer  属性,这也是异步加载 JavaScript,且等 DOM 构建完才开始执行

    线程协作

    其实如果  JavaScript 确实要修改 DOM 某节点,那也该将其放到 DOM 的构建之后

    否则有些获取元素的 操作可能就不能生效


    关于外部资源

    一个网页通常包含多个外部资源,例如图片、CSS、JavaScript、字体等等

    在解析 DOM 过程中,会按文档的顺序逐一发起请求 ,但是为了提高效率会同时运行预加载扫描器

    预加载扫描器查看已切分的标记,从中寻找资源地址并告知网络线程请求

    如此当 HTML解析被 JavaScript 阻塞时,预加载扫描器也能继续请求资源


  2. 解析 CSS 构建 CSSOM

    具体步骤如下:

    搜集:从内联样式、内部样式表、外部样式表、浏览器代理样式中,搜集所有样式规则

    构建:按上述规则构建 CSSOM 树状结构,其中每个节点表示一个样式规则,包含选择器以及样式声明

    匹配:遍历 DOM 节点匹配选择器,并为匹配的节点执行样式设置

    计算:计算 DOM 节点的最终样式,这些样式可通过以下接口查看:window.getComputedStyle()


    需要注意的是:

    上述匹配步骤,CSS 选择器会从右往左匹配 DOM 节点,举个例子,假设 CSS 选择器为 .nav span

    那么匹配之时,首先找到所有 span 节点,然后对每个 span 节点,向上找 .nav 节点


    另外注意的是:

    构建 DOM 和构建 CSSOM 是同时进行的,但是这并不意味着二者不会相互影响

    因为 CSSOM 构建会阻塞脚本执行,而脚本执行会阻塞 DOM 构建,有一个间接的影响

    线程协作
  3. 布局 (Layout)

    有了 DOM 和 CSSOM 后,需要根据二者构建出渲染树 (RenderObject Tree)

    渲染树只会包含可见元素,像 head 节点和 display 为 none 的节点,则不会出现在渲染树中


    至此,其实已经知道每个可见节点的样式,接下来需要根据渲染树进行布局计算

    即根据盒子模型和视觉格式化模型来计算每个节点在设备视口内的位置以及大小


    当页面布局和几何信息发生变化时,浏览器需要重新布局,这个过程被称为回流 (reflow)

    具体又可分为全局布局和局部布局,常见的引起回流阶段的操作如下:

    • 页面第一次进行渲染
    • 窗口的尺寸发生改变
    • 增加或删除可见元素
    • 元素的位置发生改变
    • 元素的大小发生改变

  4. 绘制 (Paint)

    绘制实际上就是遍历渲染树,在屏幕中绘制出对应的节点内容,简单来说就是个像素填充的过程

    绘制过程在多个层上完成的,这些层被称为渲染层 (Render Layer)


    渲染树中每个节点都是一个渲染对象,处于相同坐标空间 (z 轴) 的渲染对象会合并到同一个渲染层

    对于满足层叠上下文条件的渲染对象,浏览器会为其自动创建新的渲染层,常见情况如下:

    • 根元素
    • mask 属性
    • filter 属性
    • reflection 属性
    • position 属性且值为 absoluterelativefixedsticky
    • backface-visibility 属性且值为 hidden
    • transform 属性且值不为 none
    • overflow 属性且值不为 visible
    • opacity 属性且值小于 1
    • opacitytransformfilter 等属性应用动画

    满足上述条件的渲染对象拥有独立的渲染层,但这并不意味着这些渲染对象独享该渲染层

    这是因为不满足上述条件的渲染对象将会与第一个拥有渲染层的父元素共用同一个渲染层


    当元素样式有变化但不影响布局时,浏览器需要重新绘制,这个过程被称为重绘 (repaint)

    具体又可分为全局绘制和局部绘制,常见的引起重绘阶段的操作如下:

    • 修改元素 color
    • 修改元素 background-color

    值得注意的是:回流一定引起重绘,重绘不一定引起回流


  5. 合成 (Composite)

    合成阶段中,多个渲染层按照恰当的重叠顺序进行合并,而后生成位图,最终通过显卡展示在屏幕上

    在这过程中,某些渲染层会被浏览器自动提升为合成层 (Compositing Layer), 常见情况如下:

    • will-change 属性
    • position 属性且值为 fixed
    • transform 属性且值为 translate3dtranslateZ 等 3D 变换
    • canvasvideoiframe 等元素
    • opacitytransformfilter 等属性应用动画

    每个合成层拥有单独的 Graphics Layer 用于生成位图,并上传到 GPU 对其进行合成,绘制到屏幕上

    其它不是合成层的渲染层与第一个拥有 Graphics Layer 的父层级共用


    合成层有什么用?答案是硬件加速,即把某些需要频繁回流和重绘的元素单独提升成为合成层

    这样不仅可以提高绘制的效率,也能减少这些元素修改时对其它元素影响

    例如修改合成层的 transform  时,不会带来回流或重绘,只需要将多个图层再次合并,而后生成位图

    这样我们可以通过 transform:translate 修改元素位置,通过 transform:scale 修改元素大小


渲染进程的工作对浏览器性能有重要影响,常见渲染优化包括:

  1. 关键样式资源放在头部加载,善用 preloadprefetch
  2. 脚本资源通常放在脚部加载,善用 asyncdefer
  3. 尽量减少回流以及重绘操作,例如 批量修改 DOM 、离线修改 DOM 等等
  4. 对于画布,常用的技术包括有分层 canvas、离屏 canvas 等等
  5. 对于动画,使用 requireAnimationFrame 代替 setTimeout
  6. 硬件加速

类似方法有很多,这些都需要在日常开发中不断地积累,并有意识地应用到未来开发中



好啦,本文到此结束,感谢您的阅读!

如果你觉得这篇文章有需要修改完善的地方,欢迎在评论区留下你宝贵的意见或者建议

如果你觉得这篇文章还不错的话,欢迎点赞、收藏、关注,你的支持是对我最大的鼓励 (/ω\)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值