无意间在 Google Developer 上看到的文章,这是这个系列博客的第三部分[1],主要是研究渲染进程所做的事情。渲染进程涉及到 Web 性能的很多方面,这里只是概述,如果你想深入了解,可以去Web 基础的性能部分[2]看看。

渲染进程处理网页内容

渲染进程负责处理标页内的所有事情。其中,主线程负责处理大部分代码。少部分的代码可能会由工作线程处理(比如 Service Worker 或者 Web Worker)。同时,合成器线程和栅格线程也在渲染进程中运行,负责高效、流畅的呈现页面。

解析数据

构造 DOM 树

渲染进程接收到导航提交的消息后,就开始接收 HTML 数据,主线程就开始解析文本字符串(HTML),并将其转换成 DOM(Document Object Model)。

DOM 是页面在浏览器内部的结构,也是开发人员通过 JavaScript 与之交互的数据结构和 API。

解析 HTML 的规则由 HTML 标准[3]定义。同时 HTML 标准要求兼容错误的写法,如果你对这个感兴趣,可以查看 An introduction to error handling and strange cases in the parser[4] 的 HTML 部分。

子资源加载

一个网站经常会使用一些外部资源,比如 CSS、图片以及 JavaScript 等。这些文件都需要从网络获取或者是从缓存中加载。主线程在解析构建 DOM 时,会发现一个加载一个,但是这样太慢,于是为了加快速度,“预加载扫描器”会同时运行。当在文档中发现有像 <img> 或者 <link> 的内容时,预加载扫描器会将请求提交给浏览器进程中的网络线程。

JavaScript 可能阻塞解析

如果解析器碰到了 <script> 标签,就会暂停解析 HTML 文档,然后开始解析和执行 JavaScript 代码。为什么呢?因为 JavaScript 可能会通过 document.write() 这样的代码修改文档,从而改变 DOM 结构(HTML 标准里有张解析模型的图[5]非常好)。所以 HTML 解析器就必须要停下来执行 JavaScript,然后再继续解析 HTML。如果你对 JavaScript 执行的细节感兴趣,可以看看 V8 团队的分享[6]

提示浏览器如何加载资源

Web 开发者可以通过多种方式提示浏览器。如果你的 JavaScript 代码不使用 document.write(),就可以在 <script> 标签上添加 async 或者 defer 属性,这样浏览器就会异步加载运行 JavaScript 代码,而不会阻塞解析。如果可以的话,也可以使用 JavaScript 模块[7]。可以使用 <link rel="preload"> 告诉浏览器当前导航肯定需要该资源,希望尽快下载。有关信息请参阅资源优先级[8]

样式计算

光一个 DOM 结构,我们还是不知道页面长啥样子,我们还需要 CSS 来设置页面元素的样式。所以主线程会解析 CSS 来计算每个 DOM 节点长什么样子。基于 CSS 选择器,对每个元素应用相应的样式,这些都可以在 DevTools 中的 computed 中看到。

即便你不提供任何 CSS,每个 DOM 节点都会有样式。比如 <h1> 显示出来比 <h2> 大,并且每个元素都有边距。这是因为浏览器具有默认样式,如果你想知道 Chrome 默认的样式,可以到这里看源代码[9]

布局

现在渲染进程知道了文档的结构和每个节点的样式,但还是不足以渲染页面。想象一下,你给你朋友打电话描述一幅画:“画里有一个大红圈和一个蓝色小方块。”,你的朋友听了你的描述,可能还是一脸懵逼。

布局就是计算出元素之间的几何位置的过程。主线程会遍历 DOM 树和样式,然后构造出一颗布局树,这棵树上的节点都带有 x、y 坐标和边界框大小之类的信息。布局树和 DOM 树的结构类似,但是树上只包含页面可见元素的信息。如果元素被设置了 display: none,那么布局树就不会包含这个元素(visibility: hidden 的元素会被包含)。同样的,如果一个内容是通过伪类(比如 p::before { content: 'Hi!' })添加进来的,那么这个元素会被包含在布局树中,但是 DOM 树中没有。

确定页面如何布局是一项非常难的事情。即使是最简单的布局方式也要考虑字体大小、换行之类的事情,更别说浮动、隐藏溢出、修改文本显示方向等等事情了。在 Chrome 里,有一个专门负责布局的团队,感兴趣的话,可以看看这个分享[10]

绘制

有了 DOM 结构、样式、布局之后,我们还是不能渲染页面,我们还要解决渲染的顺序问题。比如,有些元素可能设置了 z-index 属性,那么按照 HTML 里面的元素顺序进行渲染就会出错。

所以在这一步,主线程会遍历布局树,并创建绘制记录。绘制记录会记录绘制过程,就像是先画背景,再画文本,最后画矩形。如果你用过 canvas,那么你可能对这个过程会很熟悉。

更新渲染管道的成本很高

渲染的过程是一个流水线,每个步骤的结果都用于下一个步骤。如果布局树变化了,那么就需要重新为受影响的部分生成绘制记录。

如果要给元素设置动画,浏览器就要在每一帧运行这些操作。大多数的显示器屏幕每秒刷新 60 次(60 fps),当每一帧都在变化的时候,人就会觉得动画很流畅,但是,如果中间丢了一些帧就会显得很卡顿。

即便渲染能跟得上屏幕刷新,但动画是在主线程上进行计算,也就是说如果主线程一旦因为执行 JavaScript 代码而被阻塞了,动画也就被卡住了。

你可以将动画涉及的 JavaScript 操作分成小块,并使用 requestAnimationFrame() 调度在每一帧上执行,更多请参考[11]。你也可以在 Web Worker 中运行 JavaScript[12]以避免阻塞主线程。

合成

如何绘制页面?

现在浏览器知道了文档结构、元素的样式、页面的几何关系以及绘制顺序,接下来就该渲染页面了。具体该怎么渲染呢?把上述信息转换成屏幕上的像素叫做栅格化。

最简单的处理方式就是把页面在当前视窗中的部分先转换成像素。如果用户滚动页面,则移动栅格化的画框,填补没有渲染的部分。Chrome 最早就是这么干的,但现代浏览器有更复杂的流程,叫做合成。

合成是将页面的各个部分进行分层,然后分别对其进行栅格化,然后通过单独的线程进行合成的技术。这样的话,当用户滚动页面的时候,因为图层都被栅格化了,所以浏览器只需要合成一个新的帧即可。动画也可以通过移动图层再合成新的帧来实现。

你可以在 DevTools 里通过 Layers 面板查看网站的分层(可以在开发者工具里找到)。

分层

为了找出哪些元素在那个图层,主线程会遍历布局树来创建图层树。如果页面的某些部分是单独的图层(比如滑入式侧边菜单)但是没有拆分出来,你可以用 CSS 里的 will-change 属性来提示浏览器进行拆分。

分层并不是越多越好,层过多可能会造成操作速度变慢,甚至还不如每帧都对页面中的小部分执行一次栅格化快,至于该怎么平衡,可以参考这里[13]

主线程的栅格化和合成

一旦创建了图层树,并确定了绘制的顺序,主线程就会将信息提交给合成线程。紧接着,合成线程会栅格化每个图层。有的情况下一个图层可能和页面一样长,因此合成线程会将它们划分成图块后发送给栅格线程。栅格线程栅格化每个图块(图块转化为位图),并将它们存到显存中。

合成线程会根据栅格线程不同的优先级处理图块,比如它会优先处理视窗(及附近)的图块。并且图块还具有不同分辨率的图块,以便在用户放大、缩放时使用。

所有的图块都栅格化后,合成线程会收集这些图块的信息(绘制图块)来创建合成帧。

  • 绘制图块:包含图块在内存中的地址、页面中的位置等相关信息
  • 合成帧:多个绘制图块的集合,绘成了页面的一帧

创建好的合成帧会通过 IPC 提交给浏览器进程。此时,可以从 UI 线程或者其他插件的渲染进程添加另一个合成帧。这些合成帧会被发送到 GPU 进行,最终展示到屏幕上。如果发生了滚动,合成线程会创建另一个合成帧发送给 GPU。

合成的好处就是和主线程无关。合成线程不需要等待样式计算或者 JavaScript 的执行,这也是为什么只需要合成的动画[14]流畅平滑的原因。如果需要再次计算布局或者绘制,就需要涉及到主线程了(这就是为什么要减少重排和重绘)。

后续还会有接下来的最后一篇 - 交互,公众号里有上两篇的内容,欢迎关注、转发、分享支持我。

http://weixin.qq.com/r/HS8mPvTEi6n0rbBd93oP (二维码自动识别)

参考资料

[1]

第三部分: https://developers.google.com/web/updates/2018/09/inside-browser-part3

[2]

Web 基础的性能部分: https://developers.google.com/web/fundamentals/performance/why-performance-matters

[3]

HTML 标准: https://html.spec.whatwg.org/

[4]

An introduction to error handling and strange cases in the parser: https://html.spec.whatwg.org/multipage/parsing.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser

[5]

HTML 标准里有张解析模型的图: https://html.spec.whatwg.org/multipage/parsing.html#overview-of-the-parsing-model

[6]

V8 团队的分享: https://mathiasbynens.be/notes/shapes-ics

[7]

JavaScript 模块: https://developers.google.com/web/fundamentals/primers/modules

[8]

资源优先级: https://developers.google.com/web/fundamentals/performance/resource-prioritization

[9]

源代码: https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/resources/html.css

[10]

分享: https://www.youtube.com/watch?v=Y5Xa4H2wtVA

[11]

更多请参考: https://developers.google.com/web/fundamentals/performance/rendering/optimize-javascript-execution

[12]

Web Worker 中运行 JavaScript: https://www.youtube.com/watch?v=X57mh8tKkgE

[13]

这里: https://developers.google.com/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count

[14]

只需要合成的动画: https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/

缩放浏览器不会换行_深入了解现代浏览器之三 - 渲染相关推荐

  1. 浏览器字体大小设置_全新内核 Edge 浏览器来了,这回或许能成为你的真 · 默认浏览器...

    1 月 16 日,此前测试已久的 Chromium 内核 Edge 浏览器稳定版本终于如期上线,至此微软的 Edge 浏览器已经基本完成了从 EdgeHTML 排版引擎到 Blink(Chromium ...

  2. nginx解决浏览器跨域问题_使用nginx解决浏览器跨域

    什么是浏览器跨域? 跨域就是指浏览器具有同源策略,浏览器不会执行访问其它网址数据的js脚本,比如说访问其它网址的cookie数据,调用其它网址的api接口. 什么是同源? 协议,域名,端口均相同的情况 ...

  3. httpservletresponse 重定向浏览器不变的原因_正确区分火狐浏览器(Firefox)中国版和国际版amp;下载方法...

    近日由于某些个人原因, 不得不从谷歌浏览器迁移到火狐浏览器, 不想再次遭遇国际版中国版之分的困扰; 研究途中, 发现一些有趣的点, 特此总结分享. 最重要的, 不必把任何软件的中国特供版都视为洪水猛兽 ...

  4. ie浏览器java 脚本下载_如何设置ie浏览器中的activex控件和插件java脚本下载用户验证...

    ActiveX是Microsoft提出的一组使用COM(ComponentObjectModel,部件对象模型)使得软件部件在网络环境中进行交互的技术集.它与具体的编程语言无关.作为针对Interne ...

  5. 火狐浏览器设置url编码_浅谈不同浏览器地址栏中编码的差异

    今天是上交学院专业 ,权当是娱乐而已,拿不拿奖就是另外一回事了.貌似这篇论文跟我的专业没什么必然的联系,倒是他们网工专业的刚好适合,但不知为什么写这类型的我就特别顺手... 摘要: 本文介绍了中文版本 ...

  6. python模拟浏览器请求的库_基于Python模拟浏览器发送http请求

    1.使用 urllib2 实现 #! /usr/bin/env python # -*- coding=utf-8 -*- import urllib2 url="https://www.b ...

  7. 【表格设置】HTML中合并单元格,对列组合应用样式,适应各浏览器的内容换行

    1.常用表格标签 普通    <table>           |           <tr>          |           |          <th ...

  8. java中浏览器电脑的分辨率_关于移动端适配,你必须要知道的

    原标题:关于移动端适配,你必须要知道的 来自公众号:code秘密花园 导读 移动端适配,是我们在开发中经常会遇到的,这里面可能会遇到非常多的问题: 1px问题 UI图完美适配方案 iPhoneX适配方 ...

  9. java二次开发浏览器内核_常见的五大浏览器的内核

    首先我们理解一下什么是内核: 英文叫做:Rendering Engine,中文翻译很多,排版引擎.解释引擎.渲染引擎,现在流行称为浏览器内核. Rendering Engine,顾名思义,就是用来渲染 ...

最新文章

  1. matlab plot3d_号称轻量级MATLAB的数学工具—Maple 2019.2
  2. c语言规定预处理命令必须以什么开头,C语言规定预处理命令必须以___________开头...
  3. 理解什么是MyBatis?
  4. 【学习笔记】JS进阶语法一事件基础
  5. tcp ip协议_网络通信-TCP/IP协议族简述
  6. java 两个线程同步_Java 多线程(二)—— 线程的同步
  7. drupal7 的安装方法
  8. 开启事物_开启大门,聚焦在正确的事物上
  9. python效率低为什么_为什么我的工作效率降低了?
  10. Kubernetes学习总结(15)—— Kubernetes 实战之部署 Mysql 集群
  11. 2020国际机器翻译大赛:火山翻译力夺五项冠军
  12. 通过一个模拟程序让你明白WCF大致的执行流程
  13. 推荐:“创建MOSS2007自定义字段类型实例”
  14. mysql自带的管理工具_mysql几个管理工具推荐
  15. 这15个网站,为设计师提供用不完的免费素材
  16. opencv-python图形图像处理入门基础知识
  17. 8421码转16进制的c语言,16进制数转换成8421BCD编码函数
  18. iPhone 可以DIY了?苹果推出自助维修计划
  19. App前端及后端接口,模拟数据及返回值
  20. Java编程专题思维导图

热门文章

  1. jq 请求本地的json_jQuery使用ajax读取本地json文件的案例
  2. VS2012/13本地发布网站详细步骤(可带数据库)
  3. python延时一秒_python如何最快毫秒速度使用requests?
  4. Python的零基础超详细讲解(第七天)-Python的数据的应用
  5. Pygame实战:风靡全球的经典泡泡龙小游戏来袭,你会喜欢嘛?(附源码)
  6. 清华大佬告诉史上最全的Java进阶书籍推荐面南背北
  7. 另外一些MySQL优化措施
  8. 教程:6、打印文件和发送邮件
  9. grub2引导linux内核,一种基于grub2的linux系统启动bootloader的制作方法与流程
  10. ibatis查询结果返回数组_在ibatis中传递和返回自定义数组对象,在java中传递和返回oracle...