{"componentChunkName":"component---src-templates-blog-post-js","path":"/browser/","result":{"data":{"site":{"siteMetadata":{"title":"😏","author":"thoamsy"}},"markdownRemark":{"id":"bc70ee51-c46d-58e1-a4f3-90c1bd75af05","html":"<p>原文系列: <a href=\"https://developers.google.cn/web/updates/2018/09/inside-browser-part1\">https://developers.google.cn/web/updates/2018/09/inside-browser-part1</a></p>\n<p>之前，我们提到了 Chrome 的多进程构架和 URL 导航的实现。在这片文章中，我们要一起去看看渲染进程到底做了什么</p>\n<p>渲染过程（Render Progress）和 Web 的性能息息相关。也因为在渲染过程中会发生很多事情，本文仅仅介绍它的冰山一角。如果你想深入的了解，<a href=\"https://developers.google.cn/web/fundamentals/performance/why-performance-matters/\">Why Performance Matters</a> 有更多的资源。</p>\n<h2 id=\"渲染过程处理-web-内容\" style=\"position:relative;\"><a href=\"#%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B%E5%A4%84%E7%90%86-web-%E5%86%85%E5%AE%B9\" aria-label=\"渲染过程处理 web 内容 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>渲染过程处理 Web 内容</h2>\n<p>渲染过程负责在 Tab 中发生的一切事情。在一次渲染过程中，主线程会处理绝大部分你为用户准备的代码。如果还包含了 Web Worker 或者 Service Worker（PWA） 的代码，这部分会由 Worker 线程来处理。<strong>Compositor（合成）</strong> 和 <strong>Raster（栅格）</strong>也属于渲染进程的一部分，使页面渲染的更为高效和平滑。</p>\n<p>渲染进程的核心任务是将 HTML，CSS，JavaScript 转化成用户可以与之交互的 Web 网页。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/renderer.png\">\nFigure 1: 渲染进程包括了一个主线程，Worker 线程，一个 Compositor 线程 和一个 Raster 线程。</p>\n<h2 id=\"parsing\" style=\"position:relative;\"><a href=\"#parsing\" aria-label=\"parsing permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Parsing</h2>\n<h3 id=\"dom-的创建过程\" style=\"position:relative;\"><a href=\"#dom-%E7%9A%84%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B\" aria-label=\"dom 的创建过程 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>DOM 的创建过程</h3>\n<p>当渲染进程接收到来自 Navigation（详情看 Part2）的提交信息并开始接受 HTML 数据的时候，主线程开始将 HTML 中的字符串转化成 <strong>D</strong>ocument <strong>O</strong>bject <strong>M</strong>odel（<strong>DOM</strong>）</p>\n<p>DOM 即是浏览器内部呈现网页的方式，同时也是一种开发者可以通过 JavaScript 与之交互的数据结构和 API。\n<a href=\"https://html.spec.whatwg.org/\">HTML Standard</a> 定义了将 HTML 转化成 DOM 的过程。你应该注意到过，浏览器从来不会在解析 HTML 的时候抛出一个错误。比如，少了 <code class=\"language-text\">&lt;/p&gt;</code> 的标签是一个合法的 HTML。像 <code class=\"language-text\">Hi! &lt;b&gt;I&#39;m &lt;i&gt;Chrome&lt;/b&gt;!&lt;/i&gt;</code> （b 标签在 i 之前就闭合了）这种错误的标记，会被浏览器看成 <code class=\"language-text\">Hi!&lt;b&gt;I&#39;m &lt;i&gt;Chrome&lt;/i&gt;&lt;/b&gt;&lt;i&gt;!&lt;/i&gt;</code> 来理解。这是因为 HTML 的标准就是特意设计成能够将错误优雅的处理掉。如果你好奇这内部的细节，可以阅读 HTML 标准中的 <a href=\"https://html.spec.whatwg.org/multipage/parsing.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser\">An introduction to error handling and strange cases in the parser</a> 这一节。</p>\n<h3 id=\"子资源加载\" style=\"position:relative;\"><a href=\"#%E5%AD%90%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD\" aria-label=\"子资源加载 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>子资源加载</h3>\n<p>一个网页通常还会有图片，CSS 和 JS 等这些外部资源。这些文件需要从网络或者缓存中被加载。主线会在解析 HTML 文件并构建 DOM 的过程中，一个一个的处理它们。但是为了提速，“（预加载扫描器）preload scanner” 会并行运行。如果存在 <code class=\"language-text\">&lt;img&gt; 和 &lt;link&gt;</code> 在 HTML 中，预加载扫描器会默默地查看这些标记，并在浏览进程中向 network 线程发送请求。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/dom.png\">\nFigure 2: 主线程解析 HTML 文档并构建 DOM 树</p>\n<h3 id=\"javascript-会堵塞解析\" style=\"position:relative;\"><a href=\"#javascript-%E4%BC%9A%E5%A0%B5%E5%A1%9E%E8%A7%A3%E6%9E%90\" aria-label=\"javascript 会堵塞解析 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>JavaScript 会堵塞解析</h3>\n<p>当 HTML 解析器找到一个 <code class=\"language-text\">script</code> 标签的时候，它会暂停解析 HTML 文档，并开始下载，解析并执行 JS 代码。为什么？因为 JS 可以使用像 <code class=\"language-text\">document.write()</code> 这种改变了整个 DOM 结构的方法来改变文档的形状（shape of the document)。这就是为什么 HTML 解析器在它可以继续解析 HTML 文档之前，必须等待 Javascript 运行结束。如果你对 JS 的执行细节好奇的话，<a href=\"https://mathiasbynens.be/notes/shapes-ics\">V8 团队有一篇关于它的博客介绍</a></p>\n<h2 id=\"暗示浏览器哪些资源是可以加载的\" style=\"position:relative;\"><a href=\"#%E6%9A%97%E7%A4%BA%E6%B5%8F%E8%A7%88%E5%99%A8%E5%93%AA%E4%BA%9B%E8%B5%84%E6%BA%90%E6%98%AF%E5%8F%AF%E4%BB%A5%E5%8A%A0%E8%BD%BD%E7%9A%84\" aria-label=\"暗示浏览器哪些资源是可以加载的 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>暗示浏览器哪些资源是可以加载的</h2>\n<p>Web 开发者有很多种方式可以按时浏览器如何恰当地加载资源。如果你的 JS 不使用 <code class=\"language-text\">document.write()</code>，你可以给 <code class=\"language-text\">script</code> 标签添加 <code class=\"language-text\">async</code> 或者 <code class=\"language-text\">defer</code> attribute。这样浏览器就异步的加载和运行 JS 代码，而不会堵塞解析过程。你同时也可以在恰当的时候使用 <a href=\"https://developers.google.cn/web/fundamentals/primers/modules\">JavaScript module</a>。<code class=\"language-text\">&lt;link ref=&quot;preload&quot;&gt;</code> 也是一种通知浏览器，当前资源可以尽快下载的方式。可以通过阅读 <a href=\"https://developers.google.cn/web/fundamentals/performance/resource-prioritization\">Resource Prioritization – Getting the Browser to Help You</a> 了解更多。</p>\n<h2 id=\"style-calculation\" style=\"position:relative;\"><a href=\"#style-calculation\" aria-label=\"style calculation permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Style calculation</h2>\n<p>因为 CSS 的存在，仅仅有一个 DOM 是不足以让浏览器了解整个网页会是怎么样的。主线程会解析 CSS，并为每一个 DOM 节点计算出最终样式。这些信息会基于 CSS 选择器来呈现每一个元素被应用了哪些样式。你可以在通过开发者工具的 <code class=\"language-text\">computed</code> 来看到这些信息。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/computedstyle.png\">\nFigure 3: 主线程解析 CSS 得到计算过的样式</p>\n<p>即使你不提供任何 CSS，每一个 DOM 节点还是会有一个被计算过的样式。<code class=\"language-text\">&lt;h1&gt;</code> 标签看上去会比 <code class=\"language-text\">&lt;h2&gt;</code> 更大，并且每一个元素的 margin 也是不一样的。这是因为浏览器维护了一个默认样式表。如果你想了解 Chrome 的默认样式，可以阅读<a href=\"https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/css/html.css\">源码</a></p>\n<h2 id=\"layout\" style=\"position:relative;\"><a href=\"#layout\" aria-label=\"layout permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Layout</h2>\n<p>现在渲染进程知道了 DOM 的结构和每一个节点的样式，但是这些信息还不足以去渲染一个页面。相信你在电话的另一头努力地给你的朋友描述一幅画。“有一个巨大的红色的圆和一个小一点的蓝色正方形”是不足以让你的朋友想象出这幅画的样子的。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/tellgame.png\">\nFigure 4: A person standing in front of a painting, phone line connected to the other person</p>\n<p>Layout 是一个找到元素的几何信息的过程。主线程通过遍历 DOM，计算样式，并创建一个包换了 x y 坐标信息和边框大小的 layout 树。Layout 树可能和 DOM 树很相似，但是它仅仅包含了在能在页面中被看到的信息。比如 <code class=\"language-text\">display: none</code> 一旦被应用到每个元素上，这个元素就不是 layout 树的一部分。相似的，如果一个伪元素 <code class=\"language-text\">p::before { content: &quot;Hi!&quot; }</code>被应用了，它虽然不会出现在 DOM 树上，但是属于 layout 树。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/layout.png\">\nFigure 5: 主线程遍历 DOM 树，通过已经计算的样式，得到 layout 树</p>\n<p>确定页面的布局是一项由挑战性的任务。即使是最简单的页面布局，如从上到下的 block flow，也必须考虑字体的大小以及在哪里划分它们，因为它们会影响段落的大小和形状，还会影响下一段所需的位置。</p>\n<p><a href='https://developers.google.cn/web/updates/images/inside-browser/part3/layout.mp4'>layout.mp4</a></p>\n<p>Figure 6: 因为一个断行的改变，layout 也跟着改变</p>\n<p>CSS 还能让元素浮动到一边，隐藏超出边界的部分，并修改写入方向。你可以想象到，layout 阶段其实是非常复杂的任务。在 Chrome 中，有专门一个团队的工程师致力于 layout 的工作。如果你想了解更多有关他们的工作细节，<a href=\"https://www.youtube.com/watch?v=Y5Xa4H2wtVA\">few talks form BlinkOn Conference</a> 记录了大量的有趣的内容。</p>\n<h2 id=\"paint\" style=\"position:relative;\"><a href=\"#paint\" aria-label=\"paint permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Paint</h2>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/drawgame.png\">\nFigure 7: 一个人拿着画笔站在画布前，想着应该先画圆还是先画正方形</p>\n<p>有了 DOM，style，layout 还不足于渲染一个页面。假设你正在努力画一幅画，你知道画的大小，形状，每一个元素所在的位置，但是你还是需要判断绘制它们的顺序。\n比如，<code class=\"language-text\">z-index</code> 可能会被设置到当前元素上，在这种情况下，如果按 HTML 定义的顺序来绘制就会得到错误的结果。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/zindex.png\">\nFigure 8: 页面的元素按 HTML 元素中定义的元素，导致了错误的结果。因为 z-index 没有被考虑在内</p>\n<p>在绘制（Paint）阶段，主线程遍历 layout 树来创建绘制记录（Paint record)。绘制记录是一个绘画过程的注解，如“先背景，接着文本，然后矩形”。如果你使用过 <code class=\"language-text\">canvas</code>，那么这个过程对你来说应该很熟悉。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/paint.png\">\nFigure 9: 主线程遍历 layout 树并生成绘制记录</p>\n<h3 id=\"updating-rendering-pipeline-is-costly\" style=\"position:relative;\"><a href=\"#updating-rendering-pipeline-is-costly\" aria-label=\"updating rendering pipeline is costly permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Updating rendering pipeline is costly</h3>\n<p><a href='https://developers.google.cn/web/updates/images/inside-browser/part3/trees.mp4'>trees.mp4</a></p>\n<p>Figure 10: 有序的生成 DOM ，Style，Layout，Paint Trees</p>\n<p>想要理解渲染一条龙，最重要的一点是<strong>每一步中，都会使用上一个操作的结果作为新的数据。</strong> 比如修改了 layout 树，那么 paint 树也需要重新生成。\n如果你打算为某些元素加动画，浏览器必须在每一帧都重复这些过程。大多数显示器 1 秒刷新 60 次屏幕（60 fps）。当物体在屏幕中移动的时候，出现在每一帧中，人眼就会认为这段动画十分平滑。然后，如果动画在某段时间内丢帧了，页面就会看上去“卡顿”</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/pagejank1.png\">\nFigure 11：Animation frames on a timeline</p>\n<p>即使你的渲染操作能跟上屏幕的刷新，但是因为这些计算是发生在主线程上，这意味着当有其他的 JS 代码运行的时候，动画就会“掉帧”</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/pagejank2.png\">\nFigure 12: 在动画帧的时间线上，有一帧被 JS 堵塞了</p>\n<p>你可以使用 <code class=\"language-text\">requestAnimationFrame</code> 将 JS 操作拆分成一小片，它们会分散在每一帧中运行。关于这个话题更深入的部分，请看 <a href=\"https://developers.google.cn/web/fundamentals/performance/rendering/optimize-javascript-execution\">Optimize JavaScript Execution</a></p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/raf.png\">\nFigure 13：在动画帧的时间线上，更小的 JS 块</p>\n<h2 id=\"compositing\" style=\"position:relative;\"><a href=\"#compositing\" aria-label=\"compositing permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Compositing</h2>\n<h3 id=\"你怎样-draw-一个页面\" style=\"position:relative;\"><a href=\"#%E4%BD%A0%E6%80%8E%E6%A0%B7-draw-%E4%B8%80%E4%B8%AA%E9%A1%B5%E9%9D%A2\" aria-label=\"你怎样 draw 一个页面 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>你怎样 draw 一个页面</h3>\n<p><a href='https://developers.google.cn/web/updates/images/inside-browser/part3/naive_rastering.mp4'>naive_rastering.mp4</a></p>\n<p>Figure 14: naive 的栅格化过程</p>\n<p>现在浏览器知道了文档结构，每一个元素的样式，页面的几何信息，和绘制顺序，接下来怎样 draw 出这个页面呢？将这些信息转化为像素并呈现在屏幕上的过程被称为<strong>栅格化</strong>。\n可能一个比较 naive 的处理这个方式是：仅仅栅格化视口的部分。如果用户滚动着页面，那么在移动帧的过程中，通过继续栅格化来填充缺失的部分。这也是 Chrome 刚发布的时候，采用的策略。然后现代浏览器会使用一种被称为合成（compositing）的更为牛逼（sophisticated）的方式。</p>\n<h3 id=\"什么是合成\" style=\"position:relative;\"><a href=\"#%E4%BB%80%E4%B9%88%E6%98%AF%E5%90%88%E6%88%90\" aria-label=\"什么是合成 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>什么是合成</h3>\n<p><a href='https://developers.google.cn/web/updates/images/inside-browser/part3/composit.mp4'>composit.mp4</a>\nFigure 15: 合成过程</p>\n<p>合成是一种用来将一个界面分为多个层，分别将它们栅格化，并在一个叫做合成器的线程中将它们重新合并成一个界面的技术。如果滚动发生了，因为每一个图层（Layer）都早已栅格化，现在说需要做的事情仅仅是合成一个新的帧。而动画也可以通过移动层并将它们组合成一个新的帧的方式来实现。\n你可以使用开发者工具中的 Layers 面板来查看你的网页是被分成了哪些层。</p>\n<h3 id=\"深入-layer\" style=\"position:relative;\"><a href=\"#%E6%B7%B1%E5%85%A5-layer\" aria-label=\"深入 layer permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>深入 Layer</h3>\n<p>为了分清楚哪些元素会在哪些图层中出现，主线程需要遍历 layout 树并创建一个 layer 树（在 Performance 面板中，这一部分被称为 Update Layer Tree）。如果界面中那些本该成为单独的层（比如抽屉菜单）却没有成为，你可以使用 CSS 属性 <code class=\"language-text\">will-change</code> 来暗示浏览器，将它提升为一个单独的层。</p>\n<p>你可能会禁不住诱惑让每一个元素都成为一个图层，但是合成大量的层反而会导致这个过程比一帧一帧的栅格化还要慢，关键才是要测量你的渲染性能。关于这个部分的更多细节，请看 <a href=\"https://developers.google.cn/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count\">Stick to Compositor-Only Properties and Manager Layer Count</a></p>\n<h3 id=\"raster-and-composite-off-of-the-main-thread\" style=\"position:relative;\"><a href=\"#raster-and-composite-off-of-the-main-thread\" aria-label=\"raster and composite off of the main thread permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Raster and composite off of the main thread</h3>\n<p>一旦 Layer 树已经创建好，并且 Paint 的顺序是确定的，那么主线程就会将这些信息提交到合成器线程中。合成器线程会栅格化每一个 Layer。有些 Layer 可以和整个页面一样大，所以合成器线程会将它们分为多个图块，接着将每一个图块发送给栅格线程。栅格线程栅格化每一个图块，并将它们存在 GPU 的内存中。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/raster.png\">\nFigure 17: 栅格线程创建多个位图块，并发送给 GPU</p>\n<p>合成线程还可以根据优先级决定，以便视口（或附近）可以先被栅格化。图层还具有多个 tiliings 用于处理放大操作等。</p>\n<p>一旦图块被栅格化，合成器线程会有一个被称为 <strong>draw quads（绘制四边形）</strong>的过程来收集这些图块的信息，以创建一个 <strong>compositor frame（合成帧）。</strong>\nCompositor frame 接着会由 IPC 提交到浏览器进程中。此时，可以从 UI 线程中添加其他的合成帧用于浏览器 UI 的改变或者渲染进程。最后这些合成帧会被发送到 GPU 以显示到屏幕上。如果有滚动事件发送，合成器线程会创建新的合成帧发送到 GPU。</p>\n<p><img src=\"https://developers.google.cn/web/updates/images/inside-browser/part3/composit.png\">\nFigure 18: 合成器线程创造合成帧的过程。帧先发送到浏览器线程然后发送到 GPU</p>\n<p>合成的优点就是它不依赖主线程。合成线程不需要等待样式的计算或者 JS 的执行。这也是为什么 <a href=\"https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/\">只改变那些会导致合成的属性</a>被认为是创建平滑动画的最佳选择。如果 layout 或者 paint 需要被再次计算，则主线程又会忙碌起来。</p>\n<h2 id=\"总结\" style=\"position:relative;\"><a href=\"#%E6%80%BB%E7%BB%93\" aria-label=\"总结 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>总结</h2>\n<p>在这篇文章中，我们探索了渲染一条龙：从解析到合成。但愿你现在有能力去越多更多有关网页性能优化的文章。\n在下一篇，也是本系列的最后一篇中，我们将会深入合成线程，一起看看当用户输入事件<code class=\"language-text\">mousemove click</code>的时候会发生什么。</p>","frontmatter":{"title":"Inside look at modern web browser（翻译）","spoiler":"谷歌搬砖","image":null,"date":"2018/10/05"}}},"pageContext":{"slug":"/browser/","previous":{"fields":{"slug":"/git/"},"frontmatter":{"title":"The Junior Git","image":null,"spoiler":null}},"next":{"fields":{"slug":"/react-lazy/"},"frontmatter":{"title":"React Lazy 的实现原理","image":null,"spoiler":"简单讨论一下 React.lazy 内部实现"}}}}}