相关文章推荐
刚分手的哑铃  ·  reactjs - Content not ...·  10 月前    · 
爱听歌的茶叶  ·  2018-12-29 有用的 ...·  1 年前    · 
怕老婆的荒野  ·  python ...·  1 年前    · 
首发于 前端
两行CSS让页面提升了近7倍渲染性能!

两行CSS让页面提升了近7倍渲染性能!

前言

对于前端人员来讲,最令人头疼的应该就是页面性能了,当用户在访问一个页面时,总是希望它能够快速呈现在眼前并且是可交互状态。如果页面加载过慢,你的用户很可能会因此离你而去。所以页面性能对于前端开发者来说可谓是重中之重,其实你如果了解页面从加载到渲染完成的整个过程,就知道应该从哪方面下手了。

嗯,不要跑偏了,今天我们主要来研究长列表页面的渲染性能

现如今的页面越来越复杂,一个页面往往承载着大量的元素,最常见的就是一些电商页面,数以万计的商品列表是怎么保证渲染不卡顿的,大家在面对这种长列表渲染的场景下,一般都会采用分页或者虚拟列表来减缓页面一次性渲染的压力,但这些方式都需要配合JS来时实现,那么有没有仅使用CSS就能够实现的方案呢?

答案是有的,它就是我们今天的主角 —— 内容可见性(content-visibility)

content-visibility

属性值

content-visibility 是CSS新增的属性,主要用来提高页面渲染性能,它可以控制一个元素是否渲染其内容,并且允许浏览器跳过这些元素的布局与渲染。
  • visible:默认值,没有任何效果,相当于没有添加 content-visibility ,渲染效果与往常一致
  • hidden:与 display: none 类似,浏览器将跳过内容渲染(跳过的是子元素的渲染,不包含自身)
  • auto:如果该元素不在屏幕上,并且与用户无关,则不会渲染其子元素

content-visibility: hidden

上面说到 content-visibility: hidden 的效果与 display: none 类似,但其实两者还是有比较大的区别的:

  • content-visibility: hidden 只是隐藏了子元素,自身不会被隐藏
  • content-visibility: hidden 隐藏内容的渲染状态会被缓存,所以当它被移除或者设为可见时,浏览器不会重新渲染,而是会应用缓存,所以对于需要频繁切换显示隐藏的元素,这个属性能够极大地提高渲染性能。


从着上面我们可以看到,添加了 content-visibility: hidden 元素的子元素确实是没有渲染,但它自身是会渲染的!

content-visibility: auto 虚拟列表

我们仔细想想,页面上虽然会有很多元素,但是它们会同时呈现在用户眼前吗,很显然是不会的,用户每次能够真实看到就只有设备可见区那些内容,对于非可见区的内容只要页面不发生滚动,用户就永远看不到。虽然用户看不到,但浏览器却会实实在在的去渲染,以至于浪费大量的性能。所以我们得想办法让浏览器不渲染非可视区的内容就能够达到提高页面渲染性能的效果。

我们上面说到的虚拟列表原理其实就跟这个类似,在首屏加载时,只加载 可视区 的内容,当页面发生滚动时,动态通过计算获得 可视区 的内容,并将 非可视区 的内容进行删除,这样就能够大大提高长列表的渲染性能。

但这个需要配合JS才能实现,现在我们可以使用CSS中 content-visibility: auto ,它可以用来跳过屏幕外的内容渲染,对于这种有大量离屏内容的长列表,可以大大减少页面渲染时间。

我们将上面的例子稍微改改:

<template>
  <div class="card_item">
    <div class="card_inner">
      <img :src="book.bookCover" class="book_cover" />
      <div class="card_item_right">
        <div class="book_title">{{ `${book.bookName}${index + 1}` }}</div>
        <div class="book_author">{{ book.catlog }}</div>
        <div class="book_tags">
          <div class="book_tag" v-for="(item, index) in book.tags" :key="index">
            {{ item }}
        <div class="book_desc">
          {{ book.desc }}
</template>
<script setup lang="ts">
import { toRefs } from "vue";
const props = defineProps<{
  book: any;
  index: any;
}>();
const { book, index } = toRefs(props);
</script>
<style lang="less" scoped>
.card_item {
  margin: 20px auto;
  content-visibility: auto;
</style>

首先是没有添加 content-visibility: auto 的效果,无论这些元素是否在可视区,都会被渲染

content-v2.png

如果我们在平常业务中这样写,用户进入到这个页面可能就直接口吐芬芳了,为了性能考虑,我们为每一个列表项加上:

.card_item {
  content-visibility: auto;

这个时候我们再来看下效果:

content-v3.png

从第10个开始,这些没在可视区的元素就没有被渲染,这可比上面那种全部元素都渲染好太多了,但是如果浏览器不渲染页面内的一些元素,滚动将是一场噩梦,因为无法正确计算页面高度。这是因为, content-visibility 会将分配给它的元素的高度( height )视为 0 ,浏览器在渲染之前会将这个元素的高度变为 0 ,从而使我们的页面高度和滚动变得混乱。


这里我们可以看到页面上的滚动条会出现抖动现象,这是因为可视区外的元素只有出现在了可视区才会被渲染,这就回导致前后页面高度会发生变化,从而出现滚动条的诡异抖动现象,这是虚拟列表基本都会存在的问题。

contain-intrinsic-size 救场

页面在滚动过程中滚动条一直抖动,这是一个不能接受的体验问题,这个时候我们可以使用 contain-intrinsic-size 来确保元素的正确渲染,同时也保留延迟渲染的好处。

contain-intrinsic-size 是与 content-visibility 配套使用的属性,它可以用来控制由 content-visibility 指定的元素自然大小

语法

此属性是以下 CSS 属性的简写:

  • contain-intrinsic-width
  • contain-intrinsic-height
/* Keyword values */
contain-intrinsic-width: none;
/* <length> values */
contain-intrinsic-size: 1000px;
contain-intrinsic-size: 10rem;
/* width | height */
contain-intrinsic-size: 1000px 1.5em;
/* auto <length> */
contain-intrinsic-size: auto 300px;
/* auto width | auto height */
contain-intrinsic-size: auto 300px auto 4rem;