当前位置: 首页 > Web前端 > CSS

深入了解现代浏览器第3部分-渲染

时间:2023-03-30 14:24:34 CSS

我在GoogleDevelopers上偶然发现的一篇文章,这是本系列博客的第三部分,主要探讨渲染过程的作用。渲染过程涉及网络性能的许多方面。这里只是一个概述。如果想了解更多可以去webfoundation的performance部分。渲染进程处理网页的内容。渲染进程负责处理标记页面中的所有事情。其中,主线程负责处理大部分代码。一小部分代码可能由工作线程(例如ServiceWorker或WebWorker)处理。同时,合成器线程和光栅线程也在渲染进程中运行,负责页面高效流畅的渲染。解析数据构建DOM树渲染进程收到导航提交的消息后,开始接收HTML数据,主线程开始解析文本字符串(HTML),并将其转化为DOM(DocumentObjectModel)。DOM是浏览器内部页面的结构,是开发人员通过JavaScript与之交互的数据结构和API。解析HTML的规则由HTML标准定义。同时,HTML标准要求兼容错误。如果您对此感兴趣,可以查看Anintroductiontoerrorhandlingandstrangecasesintheparser的HTML部分。子资源加载一个网站经常会用到一些外部资源,比如CSS、图片和JavaScript。这些文件需要从网络上获取或者从缓存中加载。当主线程解析构建DOM时,会发现一个一个加载,但是这样太慢了,所以为了加快速度,“preloadscanner”会同时运行。当在文档中发现等内容时,预加载扫描器将请求提交给浏览器进程中的网络线程。JavaScript可以阻止解析如果解析器遇到标签中添加async或defer属性,这样浏览器就会异步加载和运行JavaScript代码,而不会阻塞解析。如果可以,您也可以使用JavaScript模块。可以通过告诉浏览器当前导航肯定需要该资源,希望尽快下载。有关信息,请参阅资源优先级。样式计算只有DOM结构,我们还不知道页面长什么样,还需要CSS来设置页面元素的样式。所以主线程解析CSS来弄清楚每个DOM节点是什么样子的。根据CSS选择器为每个元素应用适当的样式,这可以在DevTools的计算中看到。即使您不提供任何CSS,每个DOM节点都会有样式。例如

显示得比

大,并且每个元素都有边距。这是因为浏览器有一个默认的样式,如果你想知道Chrome的默认样式,可以到这里查看源码。布局现在渲染器进程知道文档的结构和每个节点的样式,但仍然不足以渲染页面。想象一下,你打电话给你的朋友描述一幅画:“画中有一个红色的大圆圈和一个蓝色的小方块。”您的朋友在听到您的描述后可能仍然一头雾水。布局是确定元素之间几何位置的过程。主线程遍历DOM树和样式,并构建一个布局树,其中的节点包含x、y坐标和边界框大小等信息。布局树的结构与DOM树类似,但树只包含页面可见元素的信息。如果该元素设置为display:none,则布局树将不包含该元素(将包含visibility:hidden的元素)。同样,如果通过伪类添加元素(例如p::before{content:'Hi!'}),该元素将包含在布局树中,但不会包含在DOM树中。确定页面的布局方式可能非常困难。即使是最简单的布局方式也不得不考虑字体大小和换行之类的事情,更不用说浮动、隐藏溢出、改变文本显示方向等事情了。在Chrome中,有一个专门负责布局的团队。有兴趣的可以看看这篇分享。绘制完DOM结构、样式、布局后,还是无法渲染页面,还得解决渲染顺序问题。例如,某些元素可能设置了z-index属性,因此按照元素在HTML中的顺序呈现会出现错误。所以在这一步,主线程遍历布局树,创建绘制记录。绘图记录会记录绘图过程,就像先画背景,再画文字,最后画矩形。如果您使用过画布,那么您可能对这个过程很熟悉。更新渲染管道是昂贵的渲染是一个管道,其中每个步骤的结果都用于下一步。如果布局树发生变化,则需要为受影响的部分重新生成图纸记录。如果你想为元素设置动画,浏览器必须在每一帧运行这些操作。大多数显示器屏幕每秒刷新60次(60fps),当每一帧都在变化时,让人感觉动画很流畅,但如果中间掉帧,就会显得很卡顿。即使渲染跟得上屏幕刷新,动画也是在主线程上计算的,这意味着如果主线程因为执行JavaScript代码而阻塞,动画就会卡住。您可以将动画中涉及的JavaScript操作分成小块,并使用requestAnimationFrame()来安排在每一帧上执行。欲了解更多信息,请参阅。您还可以在WebWorker中运行JavaScript以避免阻塞主线程。合成如何绘制页面?既然浏览器知道了文档结构、元素的样式、页面的几何形状和绘制顺序,就该渲染页面了。应该如何渲染?将上述信息转化为屏幕上的像素,称为光栅化。最简单的处理方法是先将当前窗口中的页面部分转换为像素。如果用户滚动页面,则栅格化的框架将移动以填充未呈现的部分。Chrome最初是这样做的,但现代浏览器有一个更复杂的过程,称为合成。合成是对页面的各个部分进行分层、分别光栅化并通过单独的线程合成它们的技术。这样,当用户滚动页面时,由于图层被栅格化了,浏览器只需要组成一个新的框架即可。动画也可以通过移动图层和合成新帧来实现。您可以通过DevTools(位于开发者工具中)中的Layers面板查看网站的分层。分层为了找出哪些元素在哪一层,主线程遍历布局树以创建层树。如果页面的某些部分是单独的层(例如滑入式侧边菜单)但没有拆分,您可以使用CSS中的will-change属性来提示浏览器拆分它们。层数越多越好。层数太多可能会减慢操作速度。它甚至不如每帧光栅化页面的一小部分那么快。至于如何平衡,可以参考这里。在主线程上进行光栅化和合成一旦创建了图层树并确定了绘制顺序,主线程就会将信息提交给合成线程。接下来,合成线程栅格化每一层。在某些情况下,图层可能与页面一样长,因此合成线程将它们分成小块并将它们发送到光栅线程。光栅线程将每个图块光栅化(图块被转换为位图)并将它们存储在显存中。合成线程根据光栅线程的不同优先级处理图块,例如,它优先考虑视口(和附近的)图块。并且这些图块也有不同分辨率的图块,供用户放大、放大时使用。在所有图块都被栅格化后,合成线程收集这些图块的信息(绘制图块)以创建合成框架。绘图块:包含该块在内存中的地址,在页面中的位置等相关信息复合框:多个绘图块的集合,绘制成页面的一个框,创建的复合框将被提交通过IPC进程发送给浏览器。此时,可以从UI线程或另一个插件的渲染进程添加另一个复合帧。这些合成帧被发送到GPU进行处理,最后显示在屏幕上。如果发生滚动,合成线程会创建另一个合成帧以发送到GPU。synthesis的好处是和主线程无关。合成线程不需要等待样式计算或JavaScript执行,这就是为什么只需要合成的动画就流畅流畅的原因。如果需要重新计算布局或绘图,则需要主线程参与(这就是减少重排和重绘的原因)。会有下一篇最后一篇-互动,公众号包含前两篇文章的内容,欢迎关注转发分享支持我。