{
"title": "CSS 渲染性能优化",
"tags": [
"post",
"CSS"
],
"sources": [
"xlog"
],
"external_urls": [
"https://desmond-lsg.tech/CSS-xuan-ran-xing-neng-you-hua"
],
"date_published": "2023-04-19T23:21:42.237Z",
"content": "\n## content-visibility\n\n> Improve initial load time by skipping the rendering of offscreen content.\n\n通常,许多网站在其页面上有很复杂的图形界面,并且一些内容可能会超出用户浏览器视野范围之外。在这种情况下,可以使用 CSS 中的 `content-visibility` 属性来跳过屏幕外的内容渲染,以此来减少页面的渲染时间。这个功能是最新版本的 CSS 新增的一个特性,并且对于提高渲染性能影响很大。 `content-visibility` 属性可以有三个值,包括 `visible`、`auto` 和 `hidden`。但是,我们通常可以通过将 `content-visibility` 设置为 `auto` 来直接提高页面的渲染性能,尤其是当页面中存在大量的离屏内容时。本质上,这个属性 **改变了一个元素的可见性,并管理其渲染状态**。\n\n`content-visibility` 的主要功能是允许我们推迟渲染 HTML 元素。浏览器默认情况下会渲染所有可见的元素,包括视窗可见区域之外不可见的 HTML 元素。这样做可以让浏览器正确计算页面尺寸,并保持整个页面布局和滚动条的一致性。如果不渲染所有元素,滚动将变得混乱,因为浏览器无法计算页面高度。\n\n然而,`content-visibility` 会将分配给它的元素的高度视为 0,在渲染之前将其高度设置为 0。这可能导致页面高度和滚动条混乱。但是,如果已经为元素或其子元素显式设置了高度,则不会出现此问题。在没有显式设置高度的情况下,可以使用 `contain-intrinsic-size` 来确保元素正确渲染并保留延迟渲染的好处。\n\n```css\n.card {\n content-visibility: auto;\n contain-intrinsic-size: 200px;\n}\n```\n\n使用 `contain-intrinsic-size`,可以确保没有设置尺寸的 div(例如 .card)仍然占据空间,并像具有固有尺寸的单个子元素一样布局。这个属性作为一个占位符,来替代渲染内容。\n\n虽然使用 `content-visibility: auto` 可以减少页面渲染时间,但如果有很多元素都设置了这个属性,仍然可能会出现滚动条问题。\n\n除了 `content-visibility: auto` 之外,`content-visibility` 还提供另外两个值:`visible` 和 `hidden`。这使我们能够实现类似于 `display: none` 和非 none 值之间的切换,从而实现元素的显式和隐藏。\n\n在这种情况下,`content-visibility` 可以提高频繁显示或隐藏的元素的渲染性能,例如模态框的显示和隐藏。`content-visibility` 可以提供这种性能提升,这要归功于其隐藏值(hidden)的功能与其他值的不同:\n\n- `display: none`:隐藏元素并破坏其渲染状态。这意味着取消隐藏元素与渲染具有相同内容的新元素一样昂贵\n\n- `visibility: hidden`:隐藏元素并保持其渲染状态。这并不能真正从文档中删除该元素,因为它(及其子树)仍占据页面上的几何空间,并且仍然可以单击。它也可以在需要时随时更新渲染状态,即使隐藏也是如此\n\n- `content-visibility: hidden`:隐藏元素并保留其渲染状态。这意味着该元素隐藏时行为和display: none一样,但再次显示它的成本要低得多\n\nReferences:\n[https://web.dev/content-visibility/](https://web.dev/content-visibility/)\n\n\n## will-change\n\n在渲染 CSS 样式之前,CSS 渲染器需要进行准备工作,因为某些 CSS 属性需要进行大量准备才能实现渲染。这可能会导致页面出现卡顿,给用户带来不良体验。\n\n比如,网页上的动画通常需要定期渲染,包括动态元素和其他元素。传统的做法是使用 CSS 的 3D 变换(transform 中的 translate3d() 或 translateZ())来开启 GPU 加速,从而使动画更流畅。但这种方法成本较高,可能会导致动画延迟数百毫秒。\n\n现在,可以使用 CSS 的 `will-change` 属性来直接开启 GPU 加速,而无需使用 `transform` 等 Hack 技巧。该属性指示浏览器将修改特定属性,以便进行必要的优化。这意味着 `will-change` 是一种提示,它不会对使用它的元素产生任何样式上的影响。但需要注意的是,如果创建了新的层叠上下文,则可能会产生外观效果。\n\n浏览器渲染带有 `will-change` 的元素时,浏览器将为该元素创建一个单独的层。之后,它将该元素的渲染与其他优化一起委托给 GPU,即,浏览器会识别 `will-change` 属性,并优化未来与不透明相关的变化。这将使动画变得更加流畅,因为 GPU 加速接管了动画的渲染\n\n`will-change` 的使用并不复杂,它能接受的值有:\n\n- `auto`:默认值,浏览器会根据具体情况,自行进行优化\n\n- `scroll-position`:表示开发者将要改变元素的滚动位置,比如浏览器通常仅渲染可滚动元素 “滚动窗口” 中的内容。而某些内容超过该窗口(不在浏览器的可视区域内)。如果will-change显式设置了该值,将扩展渲染 “滚动窗口” 周围的内容,从而顺利地进行更长,更快的滚动(让元素的滚动更流畅)\n\n- `contents`:表示开发者将要改变元素的内容,比如浏览器常将大部分不经常改变的元素缓存下来。但如果一个元素的内容不断发生改变,那么产生和维护这个缓存就是在浪费时间。如果 `will-change` 显式设置了该值,可以减少浏览器对元素的缓存,或者完全避免缓存。变为从始至终都重新渲染元素。使用该值时需要尽量在文档树最末尾上使用,因为该值会被应用到它所声明元素的子节点,要是在文档树较高的节点上使用的话,可能会对页面性能造成较大的影响\n\n- `<custom-ident>`:表示开发者将要改变的元素属性。如果给定的值是缩写,则默认被扩展全,比如,`will-change` 设置的值是 `padding`,那么会补全所有 `padding` 的属性,如 `will-change: padding-top, padding-right, padding-bottom, padding-left;`\n\n#### 使用 `will-change` 表示该元素在未来会发生变化\n\n因此,如果你试图将 `will-change` 和动画同时使用,它将不会给你带来优化。因此,建议在父元素上使用 `will-change`,在子元素上使用动画。\n\n```css\n.animate-element-parent {\n will-change: opacity;\n}\n\n.animate-element {\n transition: opacity .2s linear\n}\n```\n\n#### 不要使用非动画元素\n\n当在一个元素上使用 `will-change` 时,浏览器会尝试将该元素移动到一个新图层,并通过 GPU 进行转换来优化它。但如果没有任何要转换的内容,则会导致资源浪费。\n\n此外,使用 `will-change` 也需要谨慎处理,MDN 网站提供了相关描述:\n\n- **不要将 `will-change` 应用于太多元素**:浏览器已经尽力优化一切可优化的东西了。一些更强大的优化措施可能与 `will-change` 结合使用,这可能会消耗大量机器资源。如果过度使用,可能会导致页面响应缓慢或占用大量资源。例如,`* { will-change: transform, opacity; }`\n\n- **适度使用**:通常情况下,当元素恢复到其初始状态时,浏览器会放弃之前的优化工作。但是,如果在样式表中显式声明will-change属性,则表示目标元素可能经常发生变化,因此浏览器将保留优化工作的时间比以前更长。最佳做法是在元素变化之前和之后通过脚本来切换will-change的值。\n\n- **不要过早应用 `will-change` 优化**:如果页面在性能方面表现良好,则不应添加 `will-change` 属性来追求微小的速度提升。`will-change` 的设计初衷是为了解决现有性能问题而不是预防性能问题。过度使用 `will-change` 会导致大量的内存占用,并使渲染过程更加复杂,因为浏览器试图准备可能存在的变化过程。这会导致更严重的性能问题。\n\n- **给它足够的工作时间**:该属性用于告知浏览器哪些属性可能会发生变化。然后浏览器可以在变化发生前尝试做出一些优化工作。因此,给浏览器足够的时间去真正地做这些优化工作非常重要。使用时需要找到一些方法提前获知元素可能发生的变化,并为其添加 `will-change` 属性。\n\n最后需要注意的是,建议在完成所有动画后,将元素的 `will-change` 删除。下面这个示例展示如何使用脚本正确地应用 `will-change` 属性的示例,在大部分的场景中,你都应该这样做。\n\n```js\nvar el = document.getElementById('element');\n\n// 当鼠标移动到该元素上时给该元素设置 will-change 属性\nel.addEventListener('mouseenter', hintBrowser);\n// 当 CSS 动画结束后清除 will-change 属性\nel.addEventListener('animationEnd', removeHint);\n\nfunction hintBrowser() {\n // 填写上那些你知道的,会在 CSS 动画中发生改变的 CSS 属性名们\n this.style.willChange = 'transform, opacity';\n}\n\nfunction removeHint() {\n this.style.willChange = 'auto';\n}\n```\n\n## 让元素及其内容尽可能独立于文档树的其余部分 (contain)\n\n> The CSS `contain` property gives you a way to explain your layout to the browser, so performance optimizations can be made. However, it does come with some side effects in terms of your layout.\n\nW3C 的 CSS Containment Module Level 2 除了提供前面介绍的 `content-visibility` 属性之外,还有另一个属性 `contain`。该属性允许我们指定特定的 DOM 元素及其子元素,使它们能够独立于整个 DOM 树结构之外。目的是让浏览器能够只对部分元素进行重绘、重排,而不必每次针对整个页面操作。也就是说,`contain` 允许浏览器只重新计算布局、样式、绘画、大小或它们的任意组合,而针对 DOM 的有限区域,而不是整个页面。\n\n在实际使用中,我们可以通过 `contain` 属性设置下面五个值中的某一个来规定元素以何种方式独立于文档树:\n\n- `layout`:该值表示元素的内部布局不受外部的任何影响,同时该元素以及其内容也不会影响以上级别。\n- `paint`:该值表示元素的子级不能在该元素的范围外显示,该元素不会有任何内容溢出(或者即使溢出了,也不会被显示)。\n- `size`:该值表示元素盒子的大小是独立于其内容,也就是说,在计算该元素盒子大小时会忽略其子元素。\n- `content`:该值是 `contain: layout paint` 的简写。\n- `strict`:该值是 `contain: layout paint size` 的简写。\n\n`contain` 的 size、layout 和 paint 提供了不同的方式来影响浏览器的渲染计算:\n\n- `size`:告诉浏览器,当其内容发生变化时,该容器不应导致页面上的位置移动。\n- `layout`:告诉浏览器,容器的后代不应该导致其容器外元素的布局改变,反之亦然。\n- `paint`:告诉浏览器,容器的内容将永远不会绘制超出容器的尺寸。如果容器是模糊的,则根本不会绘制内容。\n\n![image](ipfs://bafkreihait5tvw3r565z6jc7hdyf3qhn4gtbsa3r2s4qzvyr6rqbii2gwm)\n\nReferences:\n- [https://css-tricks.com/lets-take-a-deep-dive-into-the-css-contain-property/](https://css-tricks.com/lets-take-a-deep-dive-into-the-css-contain-property/)\n- [https://www.smashingmagazine.com/2019/12/browsers-containment-css-contain-property/](https://www.smashingmagazine.com/2019/12/browsers-containment-css-contain-property/)\n\n\n## 使用 `font-display` 解决由于字体造成的布局偏移(FOUT)\n\n在Web中,当使用非系统字体(通过 `@font-face` 规则引入的字体)时,浏览器可能无法及时获取到 Web 字体,从而使用后备系统字体来渲染文本,这可能会导致未编排(Unstyled)的文本出现闪烁,并使整个页面布局偏移一下(FOUT)。\n\n幸运的是,CSS 的 `font-display` 属性定义了浏览器如何加载和显示字体文件,允许文本在字体加载或加载失败时显示回退字体。这可以提高性能,通过折中无样式文本闪烁使文本可见,而不是白屏等待。\n\nCSS 的 `font-display` 属性有五个值:\n\n- `auto`:默认值。使用自定义字体的文本将被隐藏,直到字体加载完成才会显示,与大多数浏览器的默认策略相似。\n- `block`:给字体一个较短的阻塞时间和无限大的交换时间,在字体加载完成前绘制“隐形”文本;一旦字体加载完成,立即切换字体。只有当使用特定字体渲染文本对页面很重要时,才应该使用 block。\n- `swap`:阻塞时间为 0,交换时间无限大,在字体加载完成前立即绘制文字;一旦字体加载成功,立即切换字体。只有当使用特定字体渲染文本对页面很重要,且使用其他字体渲染仍将显示正确信息时,才应使用 swap。\n- `fallback`:在较短的时间内,需要使用自定义字体渲染的文本不可见;如果字体还未加载完成,则先加载无样式的文本。一旦字体加载成功,文本将被正确赋予样式。当等待时间过久时,页面将一直使用后备字体。如果希望用户尽快开始阅读,而且不因新字体的载入导致文本样式发生变动而干扰用户体验,fallback 是一个很好的选择。\n- `optional`:与 fallback 类似,都是在极短的时间内文本不可见,然后再加载无样式的文本。但是,optional 选项可以让浏览器自由决定是否使用自定义字体,这取决于浏览器的连接速度。如果速度很慢,那么自定义字体可能不会被使用。使用 optional 时,阻塞时间应该非常小,交换时间为 0。\n\n\n![image](ipfs://bafkreibbek2inlulamqxlgtr4g7hqpt5qj3itvydh6ix5rgsqwprxsphlu)\n\n```css\n@font-face {\n font-family: \"Open Sans Regular\";\n font-weight: 400;\n font-style: normal;\n src: url(\"fonts/OpenSans-Regular-BasicLatin.woff2\") format(\"woff2\");\n font-display: swap;\n}\n```\n\nReferences:\n- [https://iamschulz.com/a-deep-dive-into-webfonts/](https://iamschulz.com/a-deep-dive-into-webfonts/)\n- [https://simonhearne.com/2021/layout-shifts-webfonts/](https://simonhearne.com/2021/layout-shifts-webfonts/)\n- [https://css-tricks.com/the-best-font-loading-strategies-and-how-to-execute-them/](https://css-tricks.com/the-best-font-loading-strategies-and-how-to-execute-them/)\n- [https://calendar.perfplanet.com/2020/a-font-display-setting-for-slow-connections/](https://calendar.perfplanet.com/2020/a-font-display-setting-for-slow-connections/)\n- [https://css-tricks.com/how-to-load-fonts-in-a-way-that-fights-fout-and-makes-lighthouse-happy/](https://css-tricks.com/how-to-load-fonts-in-a-way-that-fights-fout-and-makes-lighthouse-happy/)\n- [https://nooshu.github.io/blog/2021/01/23/the-importance-of-font-face-source-order-when-used-with-preload/](https://nooshu.github.io/blog/2021/01/23/the-importance-of-font-face-source-order-when-used-with-preload/)\n- [https://csswizardry.com/2020/05/the-fastest-google-fonts/](https://csswizardry.com/2020/05/the-fastest-google-fonts/)\n- [https://www.zachleat.com/web/comprehensive-webfonts/](https://www.zachleat.com/web/comprehensive-webfonts/)\n\n\n## scroll-behavior\n\nscroll-behavior 是 CSSOM View Module 提供的一个新特性,可以帮助我们实现流畅的滚动效果。该属性可以为一个滚动框指定滚动行为,不会影响由用户操作产生的其他任何滚动。\n\nscroll-behavior 有两个值:\n- `auto`:表示滚动框立即滚动。\n- `smooth`:表示滚动框使用定义的时间函数,在一段用户代理定义的时间段内平滑地滚动。请注意,如果存在,则用户代理平台应遵循约定。\n\n\n## 开启GPU渲染动画\n\n浏览器针对处理 CSS 动画和不会很好地触发重排(因此也导致绘)的动画属性进行了优化。为了提高性能,可以将被动画化的节点从主线程移到GPU上。将导致合成的属性包括 3D transforms (`transform: translateZ()`, `rotate3d()`,等),animating, transform 和 opacity, position: fixed,will-change,和 filter。一些元素,例如 `<video>`, `<canvas>` 和 `<iframe>`,也位于各自的图层上。将元素提升为图层(也称为合成)时,动画转换属性将在GPU中完成,从而改善性能,尤其是在移动设备上。\n\n\n## 减少渲染阻止时间\n\n大样式表分解成多个样式表,只让主CSS文件阻塞关键路径,并以高优先级下载它,而让其他样式表以低优先级方式下载。\n\n```html\n<!-- style.css contains only the minimal styles needed for the page rendering -->\n<link rel=\"stylesheet\" href=\"styles.css\" media=\"all\" />\n\n<!-- Following stylesheets have only the styles necessary for the form factor -->\n<link rel=\"stylesheet\" href=\"sm.css\" media=\"(min-width: 20em)\" />\n<link rel=\"stylesheet\" href=\"md.css\" media=\"(min-width: 64em)\" />\n<link rel=\"stylesheet\" href=\"lg.css\" media=\"(min-width: 90em)\" />\n<link rel=\"stylesheet\" href=\"ex.css\" media=\"(min-width: 120em)\" />\n<link rel=\"stylesheet\" href=\"print.css\" media=\"print\" />\n```\n\n默认情况下,浏览器假设每个指定的样式表都是阻塞渲染的。通过添加 media 属性附加媒体查询,告诉浏览器何时应用样式表。当浏览器看到一个它知道只会用于特定场景的样式表时,它仍会下载样式,但不会阻塞渲染。通过将 CSS 分成多个文件,主要的 阻塞渲染 文件(本例中为 styles.css)的大小变得更小,从而减少了渲染被阻塞的时间。",
"attributes": [
{
"value": "CSS-xuan-ran-xing-neng-you-hua",
"trait_type": "xlog_slug"
}
]
}