引用

《浏览器工作原理与实践》

本文主要讲解渲染引擎的分层和合成机制,因为分层和合成机制代表了浏览器最为先进的合成技术,Chrome 团队为了做到这一点,做了大量的优化工作。了解其工作原理,有助于拓宽你的视野,而且也有助于你更加深刻地理解 CSS 动画和 JavaScript 底层工作机制。

一、显示器是怎么显示图像的

每个显示器都有固定的刷新频率,通常是 60HZ,也就是每秒更新 60 张图片,更新的图片都来自于显卡中一个叫前缓冲区的地方,显示器所做的任务很简单,就是每秒固定读取 60 次前缓冲区中的图像,并将读取的图像显示到显示器上。

那么这里显卡做什么呢?

显卡的职责就是合成新的图像,并将图像保存到后缓冲区中,一旦显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样就能保证显示器能读取到最新显卡合成的图像。通常情况下,显卡的更新频率和显示器的刷新频率是一致的。但有时候,在一些复杂的场景中,显卡处理一张图片的速度会变慢,这样就会造成视觉上的卡顿。

二、帧 VS 帧率

了解了显示器是怎么显示图像的之后,下面再来明确下帧和帧率的概念,因为这是后续一切分析的基础。

当你通过滚动条滚动页面,或者通过手势缩放页面时,屏幕上就会产生动画的效果。之所以你能感觉到有动画的效果,是因为在滚动或者缩放操作时,渲染引擎会通过渲染流水线生成新的图片,并发送到显卡的后缓冲区。

大多数设备屏幕的更新频率是 60 次 / 秒,这也就意味着正常情况下要实现流畅的动画效果,渲染引擎需要每秒更新 60 张图片到显卡的后缓冲区。

把渲染流水线生成的每一副图片称为一帧把渲染流水线每秒更新了多少帧称为帧率,比如滚动过程中 1 秒更新了 60 帧,那么帧率就是 60Hz(或者 60FPS)。

由于用户很容易观察到那些丢失的帧,如果在一次动画过程中,渲染引擎生成某些帧的时间过久,那么用户就会感受到卡顿,这会给用户造成非常不好的印象。

要解决卡顿问题,就要解决每帧生成时间过久的问题,为此 Chrome 对浏览器渲染方式做了大量的工作,其中最卓有成效的策略就是引入了分层和合成机制。分层和合成机制代表了当今最先进的渲染技术,所以接下来就来分析下什么是合成和渲染技术

三、如何生成一帧图像

不过在开始之前,还需要聊一聊渲染引擎是如何生成一帧图像的。这需要回顾下前面《06 | 渲染流程(下):HTML、CSS 和 JavaScript 文件,是如何变成页面的?》介绍的渲染流水线。关于其中任意一帧的生成方式,有重排重绘合成三种方式

这三种方式的渲染路径是不同的,通常渲染路径越长,生成图像花费的时间就越多。比如重排,它需要重新根据 CSSOM 和 DOM 来计算布局树,这样生成一幅图片时,会让整个渲染流水线的每个阶段都执行一遍,如果布局复杂的话,就很难保证渲染的效率了。而重绘因为没有了重新布局的阶段,操作效率稍微高点,但是依然需要重新计算绘制信息,并触发绘制操作之后的一系列操作。

相较于重排和重绘,合成操作的路径就显得非常短了,并不需要触发布局和绘制两个阶段,如果采用了 GPU,那么合成的效率会非常高。

所以,关于渲染引擎生成一帧图像的几种方式,按照效率推荐合成方式优先,若实在不能满足需求,那么就再退后一步使用重绘或者重排的方式。

本文的焦点在合成上,所以接下来就来深入分析下 Chrome 浏览器是怎么实现合成操作的。Chrome 中的合成技术,可以用三个词来概括总结:分层分块合成

四、分层和合成

通常页面的组成是非常复杂的,有的页面里要实现一些复杂的动画效果,比如点击菜单时弹出菜单的动画特效,滚动鼠标滚轮时页面滚动的动画效果,当然还有一些炫酷的 3D 动画特效。如果没有采用分层机制,从布局树直接生成目标图片的话,那么每次页面有很小的变化时,都会触发重排或者重绘机制,这种“牵一发而动全身”的绘制策略会严重影响页面的渲染效率。

为了提升每帧的渲染效率,Chrome 引入了分层和合成的机制。那该怎么来理解分层和合成机制呢?

你可以把一张网页想象成是由很多个图片叠加在一起的,每个图片就对应一个图层,Chrome 合成器最终将这些图层合成了用于显示页面的图片。如果你熟悉 PhotoShop 的话,就能很好地理解这个过程了,PhotoShop 中一个项目是由很多图层构成的,每个图层都可以是一张单独图片,可以设置透明度、边框阴影,可以旋转或者设置图层的上下位置,将这些图层叠加在一起后,就能呈现出最终的图片了。

在这个过程中,将素材分解为多个图层的操作就称为分层,最后将这些图层合并到一起的操作就称为合成。所以,分层和合成通常是一起使用的。

考虑到一个页面被划分为两个层,当进行到下一帧的渲染时,上面的一帧可能需要实现某些变换,如平移、旋转、缩放、阴影或者 Alpha 渐变,这时候合成器只需要将两个层进行相应的变化操作就可以了,显卡处理这些操作驾轻就熟,所以这个合成过程时间非常短。

理解了为什么要引入合成和分层机制,下面再来看看 Chrome 是怎么实现分层和合成机制的。

在 Chrome 的渲染流水线中,分层体现在生成布局树之后,渲染引擎会根据布局树的特点将其转换为层树(Layer Tree),层树是渲染流水线后续流程的基础结构。

层树中的每个节点都对应着一个图层,下一步的绘制阶段就依赖于层树中的节点。在《06 | 渲染流程(下):HTML、CSS 和 JavaScript 文件,是如何变成页面的?》中介绍过,绘制阶段其实并不是真正地绘出图片,而是将绘制指令组合成一个列表,比如一个图层要设置的背景为黑色,并且还要在中间画一个圆形,那么绘制过程会生成|Paint BackGroundColor:Black | Paint Circle|这样的绘制指令列表,绘制过程就完成了。

有了绘制列表之后,就需要进入光栅化阶段了,光栅化就是按照绘制列表中的指令生成图片。每一个图层都对应一张图片,合成线程有了这些图片之后,会将这些图片合成为“一张”图片,并最终将生成的图片发送到后缓冲区。这就是一个大致的分层、合成流程。

需要重点关注的是,合成操作是在合成线程上完成的,这也就意味着在执行合成操作时,是不会影响到主线程执行的。这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因

五、分块

如果说分层是从宏观上提升了渲染效率,那么分块则是从微观层面提升了渲染效率

通常情况下,页面的内容都要比屏幕大得多,显示一个页面时,如果等待所有的图层都生成完毕,再进行合成的话,会产生一些不必要的开销,也会让合成图片的时间变得更久。

因此,合成线程会将每个图层分割为大小固定的图块,然后优先绘制靠近视口的图块,这样就可以大大加速页面的显示速度。不过有时候, 即使只绘制那些优先级最高的图块,也要耗费不少的时间,因为涉及到一个很关键的因素——纹理上传,这是因为从计算机内存上传到 GPU 内存的操作会比较慢。

为了解决这个问题,Chrome 又采取了一个策略:在首次合成图块的时候使用一个低分辨率的图片。比如可以是正常分辨率的一半,分辨率减少一半,纹理就减少了四分之三。在首次显示页面内容的时候,将这个低分辨率的图片显示出来,然后合成器继续绘制正常比例的网页内容,当正常比例的网页内容绘制完成后,再替换掉当前显示的低分辨率内容。这种方式尽管会让用户在开始时看到的是低分辨率的内容,但是也比用户在开始时什么都看不到要好

六、如何利用分层技术优化代码

通过上面的介绍,相信你已经理解了渲染引擎是怎么将布局树转换为漂亮图片的,理解其中原理之后,你就可以利用分层和合成技术来优化代码了。

在写 Web 应用的时候,你可能经常需要对某个元素做几何形状变换、透明度变换或者一些缩放操作,如果使用 JavaScript 来写这些效果,会牵涉到整个渲染流水线,所以 JavaScript 的绘制效率会非常低下。

这时你可以使用 will-change 来告诉渲染引擎你会对该元素做一些特效变换,CSS 代码如下:

.box {
will-change: transform, opacity;
}

这段代码就是提前告诉渲染引擎 box 元素将要做几何变换和透明度变换操作,这时候渲染引擎会将该元素单独实现一层,等这些变换发生时,渲染引擎会通过合成线程直接去处理变换,这些变换并没有涉及到主线程,这样就大大提升了渲染的效率。这也是 CSS 动画比 JavaScript 动画高效的原因。

所以,如果涉及到一些可以使用合成线程来处理 CSS 特效或者动画的情况,就尽量使用 will-change 来提前告诉渲染引擎,让它为该元素准备独立的层。但是凡事都有两面性,每当渲染引擎为一个元素准备一个独立层的时候,它占用的内存也会大大增加,因为从层树开始,后续每个阶段都会多一个层结构,这些都需要额外的内存,所以你需要恰当地使用 will-change。

七、总结

  • 首先介绍了显示器显示图像的原理,以及帧和帧率的概念,然后基于帧和帧率又介绍渲染引擎是如何实现一帧图像的。通常渲染引擎生成一帧图像有三种方式:重排、重绘和合成。其中重排和重绘操作都是在渲染进程的主线程上执行的,比较耗时;而合成操作是在渲染进程的合成线程上执行的,执行速度快,且不占用主线程。
  • 然后重点介绍了浏览器是怎么实现合成的,其技术细节主要可以使用三个词来概括:分层、分块和合成。
  • 最后还讲解了 CSS 动画比 JavaScript 动画高效的原因,以及怎么使用 will-change 来优化动画或特效。

思考时间

观察下面代码,结合 Performance 面板、内存面板和分层面板,全面比较在 box 中使用 will-change 和不使用 will-change 的效率、性能和内存占用等情况。

<html>
<head><title> 观察 will-change</title><style>.box {will-change: transform, opacity;display: block;float: left;width: 40px;height: 40px;margin: 15px;padding: 10px;border: 1px solid rgb(136, 136, 136);background: rgb(187, 177, 37);border-radius: 30px;transition: border-radius 1s ease-out;}body {font-family: Arial;}</style>
</head><body><div id="controls"><button id="start">start</button><button id="stop">stop</button></div><div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div><div class="box"> 旋转盒子 </div></div><script>let boxes = document.querySelectorAll('.box');  let boxes1 = document.querySelectorAll('.box1');  let start = document.getElementById('start');let stop = document.getElementById('stop');let stop_flag = falsestart.addEventListener('click', function () {stop_flag =  falserequestAnimationFrame(render);})stop.addEventListener('click', function () {stop_flag = true})let  rotate_ = 0let opacity_ = 0function render() { if(stop_flag)return 0 rotate_ = rotate_ + 6if( opacity_ > 1)opacity_ = 0opacity_ = opacity_ + 0.01let command =  'rotate('+rotate_ + 'deg)';for (let index = 0; index < boxes.length; index++) {boxes[index].style.transform  = commandboxes[index].style.opacity = opacity_}requestAnimationFrame(render);}</script>
</body></html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/88371.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/88371.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/88371.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数字后端】-什么是RC corner? 每种Corner下有什么区别?

芯片的寄生参数可以在多个corner下提取&#xff0c;他们对应了不同情况的net delay Typical&#xff1a;R和C都是标准值Cmax(Cworst)&#xff1a;C最大的互连角&#xff0c;R小于TypicalCmin(Cbest)&#xff1a;C最小&#xff0c;R大于TypicalRCmax(RCworst)&#xff1a;互连线…

HarmonyOS开发基础 --鸿蒙仓颉语言基础语法入门

仓颉编程语言是华为自主研发的一种面向全场景应用开发的现代编程语言&#xff0c;具有高效编程、安全可靠、轻松并发和卓越性能等特点。本节将简单介绍仓颉语言的部分语法和使用&#xff0c;帮助开发者快速上手。 1.3.1&#xff1a;数据类型 整数 仓颉把整数分为有符号和无符…

Excel文件比较器v1.3,html和js写的

Excel文件比较器v1.3 版本说明&#xff1a;v1.3 1添加支持文件格式&#xff1a;CSV。 2&#xff0c;添加60条历史记录保存功能 - 用于保存比对结果。历史记录保存在浏览器的localStorage中&#xff0c;这是一个浏览器提供的本地存储机制&#xff0c;数据会一直保留直到用户…

Kimi“新PPT助手” ,Kimi全新自研的免费AI生成PPT助手

大家好&#xff0c;这里是K姐。 一个帮你用AI轻松生成精美PPT的女子。 前段时间给大家分享了一期用智能体做PPT的对比测评&#xff0c;很多友友都表示&#xff1a;那 Kimi 呢&#xff1f; 今天偶然发现 Kimi 新增了一个叫“新PPT助手”的功能&#xff0c;立马上手体验了一下…

MySQL DATETIME类型存储空间详解:从8字节到5字节的演变

在MySQL数据库设计中&#xff0c;DATETIME类型用于存储日期和时间信息&#xff0c;但其存储空间大小并非固定不变&#xff0c;而是随MySQL版本迭代和精度定义动态变化。本文将详细说明其存储规则&#xff0c;并提供清晰的对比表格。 一、核心结论 MySQL 5.6.4 是分水岭&#…

Gartner发布中国企业应用生成式AI指南:避免12 个 GenAI 陷阱

GenAI 技术&#xff08;例如 AI 代理和 DeepSeek&#xff09;的快速迭代导致企业抱有不切实际的期望。本研究借鉴了我们与中国 AI 领导者就常见的 GenAI 陷阱进行的讨论&#xff0c;并提供了最终有助于成功采用的建议。 主要发现 接受调查的首席信息官表示&#xff0c;生成式人…

Vue3中ref和reactive的区别与使用场景详解

在 Vue 3 中&#xff0c;响应式系统进行了全新设计&#xff0c;ref 和 reactive 是其中的核心概念。 ### 一、ref 的使用 ref 适用于基本数据类型&#xff0c;也可以用于对象&#xff0c;但返回的是一个带 .value 的包装对象。 js import { ref } from vue const count ref(…

React性能优化:父组件如何导致子组件重新渲染及避免策略

目录 React性能优化&#xff1a;父组件如何导致子组件重新渲染及避免策略什么是重新渲染&#xff1f;父组件如何"无辜"地让子组件重新渲染&#xff1f;示例 1: 基础父组件状态变更示例 2: 传递未变化的原始类型Prop示例 3: 传递引用类型Prop&#xff08;对象&#xf…

图的拓扑排序管理 Go 服务启动时的组件初始化顺序

在构建复杂的 Go 应用程序时&#xff0c;服务的启动过程往往涉及多个组件的初始化&#xff0c;例如日志、配置、数据库连接、缓存、服务管理器、适配器等等。这些组件之间通常存在着复杂的依赖关系&#xff1a;日志可能需要配置信息&#xff0c;数据库连接可能依赖日志和追踪&a…

【物理重建】SPLART:基于3D高斯泼溅的铰链估计与部件级重建

标题&#xff1a;《SPLART: Articulation Estimation and Part-Level Reconstruction with 3D Gaussian Splatting》 项目&#xff1a;https://github.com/ripl/splart 文章目录 摘要一、引言二、相关工作2.1 数据驱动的铰链学习2.2 物体重建的表征方法2.3 铰链物体重建 三、方…

vscode中vue自定义组件的标签失去特殊颜色高亮

遇到的问题 最近接触了一个历史遗留项目时&#xff0c;我遭遇了堪称"史诗级屎山"的代码结构——各种命名混乱的自定义组件和原生HTML标签混杂在一起&#xff0c;视觉上完全无法区分。这让我突然想起&#xff0c;之前在使用vue或者其他框架开发的时候&#xff0c;只要…

【Dify精讲】第19章:开源贡献指南

今天&#xff0c;让我们深入 Dify 的开源贡献体系&#xff0c;看看这个项目是如何在短短时间内聚集起一个活跃的开发者社区的。作为想要参与 Dify 开发的你&#xff0c;这一章将是你的实战指南。 一、代码贡献流程&#xff1a;从想法到合并的完整路径 1.1 贡献前的准备工作 …

Web攻防-CSRF跨站请求伪造Referer同源Token校验复用删除置空联动上传或XSS

知识点&#xff1a; 1、Web攻防-CSRF-原理&检测&利用&防御 2、Web攻防-CSRF-防御-Referer策略隐患 3、Web攻防-CSRF-防御-Token校验策略隐患 一、演示案例-WEB攻防-CSRF利用-原理&构造 CSRF 测试功能点 删除帐户 更改电子邮件 如果不需要旧密码&#xff0c;请…

Drag-and-Drop LLMs: Zero-Shot Prompt-to-Weights

“拖拽式大模型定制”&#xff08;Drag-and-Drop LLMs: Zero-Shot Prompt-to-Weights&#xff09;。 核心问题&#xff1a; 现在的大模型&#xff08;比如GPT-4&#xff09;很厉害&#xff0c;但想让它们专门干好某个特定任务&#xff08;比如解数学题、写代码&#xff09;&am…

抖音视频怎么去掉抖音号水印保存

随着抖音成为短视频平台的领军者&#xff0c;越来越多的人喜欢在上面拍摄、观看和分享各种创意内容。对于用户来说&#xff0c;下载抖音视频并去除水印保存&#xff0c;以便后续使用或分享成为了一种常见需求。抖音号水印的存在虽然能帮助平台追溯视频源头&#xff0c;但也让许…

【RAG技术(1)】大模型为什么需要RAG

文章目录 为什么需要RAG&#xff1f;RAG的工作原理关键的Embedding技术 RAG vs 模型微调&#xff1a;选择的核心逻辑RAG的关键挑战与解决思路1. 检索质量决定一切2. 上下文长度限制 实际应用场景分析企业知识问答技术文档助手法律咨询系统 构建RAG系统的关键步骤总结 为什么需要…

JS红宝书笔记 - 8.1 理解对象

对象就是一组没有特定顺序的值&#xff0c;对象的每个属性或者方法都可由一个名称来标识&#xff0c;这个名称映射到一个值。可以把对象想象成一张散列表&#xff0c;其中的内容就是一组名值对&#xff0c;值可以是数据或者函数 创建自定义对象的通常方式是创建Object的一个新…

Meson介绍及编译Glib库

一.概述 1.Meson 的简介 Meson&#xff08;The Meson Build System&#xff09;是个项目构建系统&#xff0c;类似的构建系统有 Makefile、CMake、automake …。 Meson 是一个由 Python 实现的开源项目&#xff0c;其思想是&#xff0c;开发人员花费在构建调试上的每一秒都是…

Qt元对象系统实践指南:从入门到应用

目录 摘要 元对象系统核心概念 项目示例&#xff1a;动态UI配置工具 元对象系统在项目中的应用 1. 信号与槽机制 2. 动态属性系统 3. 运行时反射能力 4. 属性绑定与响应 实际项目应用场景 动态UI配置 对象序列化 插件系统 性能优化建议 结论 参考资料 摘要 本文…

Kafka 与其他 MQ 的对比分析:RabbitMQ/RocketMQ 选型指南(一)

消息队列简介 在当今的分布式系统架构中&#xff0c;消息队列&#xff08;Message Queue&#xff0c;MQ&#xff09;扮演着举足轻重的角色。随着业务规模的不断扩大和系统复杂度的日益提升&#xff0c;各个组件之间的通信和协同变得愈发关键 。消息队列作为一种异步的通信机制…