One of the biggest headaches for front-end developers is page performance. When users visit a page, they always want it to be fast and interactive. If the page loads too slowly, your users will probably leave you. So page performance for front-end developers can be said to be a top priority, in fact, if you understand the page from loading to rendering the entire process is completed, you know where to start.
Well, don’t get sidetracked, today we’re going to focus on the rendering performance of long list pages
Nowadays, the page is more and more complex, a page often carries a large number of elements, the most common is some of the e-commerce page, tens of thousands of product list is how to ensure that the rendering is not lagging, we are in the face of such a long list of rendering scenarios, generally use paging or virtual list to slow down the pressure of one-time rendering of the page, but these ways need to be combined with the JS to be realized, then there is no only CSS alone can realize the program?
The answer is yes, and it is our main character today — content-visibility.
content-visibility
content-visibility
is a new property added to CSS that is primarily used to improve page rendering performance by controlling whether or not an element renders its content and allowing the browser to skip the layout and rendering of these elements.
visible: default value, no effect. The content of the element is laid out and rendered normally.
hidden: the element skips its content. Skipped content cannot be accessed by user-agent functions such as find on page, tabbed navigation, etc., nor can it be selected or focused. This is similar to settingdisplay: none
to content.
auto: This element turns on layout containment, style containment, and draw containment. It also skips content if the element is not relevant to the user. Unlike hidden, the skipped content must still be available for normal user-agent functions such as find on page, tabbed navigation, etc., and must be normally focusable and selectable.
content-visibility: hidden manually manages visibility
It was mentioned above that the effect of content-visibility: hidden
is similar to that of display: none
, but there is actually a relatively big difference between the two:
content-visibility: hidden only hides the child element, not itself.
content-visibility: hidden The rendering state of hidden content is cached, so when it is removed or made visible, the browser does not re-render it, but applies the cache instead, so for elements that require frequent toggling between showing and hiding, this attribute can greatly improve rendering performance.
From this we can see that the child element with the content-visibility: hidden
element added is indeed not rendered, but it does render itself!
content-visibility: auto skips rendering
Let’s think about it, although there will be a lot of elements on the page, but they will be presented in front of the user at the same time, it is clear that it will not be, the user can really see each time only the visible area of the device those contents, for the non-visible area of the contents of the content as long as the page does not happen to scroll, the user will never see. Although the user can not see, but the browser will actually go to the rendering, so that a lot of wasted performance. So we have to find a way to make the browser does not render the content of the non-visible area can achieve the effect of improving page rendering performance.
The principle of the virtual list we mentioned above is actually similar to this one, when loading the first screen, only the content of 可视区
is loaded, and when the page scrolls, the content of 可视区
is dynamically calculated and the content of 非可视区
is deleted, which greatly improves the rendering performance of the long list.
But this needs to be done in conjunction with JS, and now we can use CSS at content-visibility: auto
, which can be used to skip the rendering of off-screen content, and for such long lists with a lot of off-screen content, the page rendering time can be greatly reduced.
Let’s change the above example slightly:
<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>
</div>
<div class="book_desc">
{{ book.desc }}
</div>
</div>
</div>
</div>
</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>
The first is the effect of not adding content-visibility: auto
, which renders these elements whether they are in the visual area or not
If we write it this way in our normal business, the user may just mouth off when they get to this page, and for performance reasons, we add for each list item:
.card_item {
content-visibility: auto;
}
This time we’ll look at the results:
From the 10th onwards, those elements that are not in the visual area are not rendered, which is much better than rendering all of them, but if the browser doesn’t render some of the elements within the page, scrolling is a nightmare, because it can’t calculate the page height correctly. This is because content-visibility
treats the height of the element assigned to it ( height
) as 0
and the browser changes the height of this element to 0
before rendering, thus confusing our page height and scrolling.
Here we can see the scroll bar on the page will appear jitter phenomenon, this is because the visual area outside the element only appears in the visual area will be rendered, which will lead back to the front and back of the page height will change, thus appearing the scroll bar of the weird jitter phenomenon, this is the virtual list of the basic problems will exist.
⚠️ Note: When the element is close to the viewport, the browser no longer adds the size
container and starts drawing and hit-testing the element’s content. This allows the rendering to complete in time for the user to view it.
This is why we saw above that the child element is not rendered until the tenth, because it needs a buffer so that the browser can render it in time for the user to see it when scrolling occurs.
The size
mentioned above is actually a potential value for the CSS property contain
, which refers to the fact that the size limit on an element ensures that the element’s box can be laid out without needing to check its descendants. This means that if we only need the size of the element, we can skip the layout of the descendants.
contain-intrinsic-size
It’s an unacceptable experience problem that the scrollbar keeps jittering during page scrolling, and to better implement content-visibility
, browsers need to apply size containment to ensure that the rendering result of the content doesn’t affect the size of the element in any way. This means that the element will be laid out as if it were empty. If the element does not have the height specified in a regular block layout, then it will be 0 height.
At this point we can use contain-intrinsic-size
to specify the natural size of the element, ensuring that the divs of our unrendered children still take up space while retaining the benefits of deferred rendering.
This property is a shorthand for the following CSS properties:
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;
contain-intrinsic-size can specify one or both of the following values for an element. If two values are specified, the first value applies to the width and the second value applies to the height. If a single value is specified, it applies to both width and height.
We only need to add contain-intrinsic-size
to the element added content-visibility: auto
to be able to solve the problem of scroll bar jitter, of course, the height of this height is about close to the height of the real rendering, the better the effect will be, if we really can not know the exact height, we can also give an approximate value, but also will make the scroll bar problem is relatively reduced.
.card_item {
content-visibility: auto;
contain-intrinsic-size: 200px;
}
When we didn’t add the contain-intrinsic-size
attribute, the heights of the elements outside the visual area were all 0. Now the heights of these elements are all the same as the value we set for contain-intrinsic-size
, in which case the height of the entire page won’t change (or rather, will change very little), and the page scrollbar won’t jitter (or rather, will jitter less).
performance comparison
Having said that, is content-visibility
really able to improve the rendering performance of the page, let’s actually compare it:
- First is the rendering of the page without
content-visibility
- Then there’s the page rendering with
content-visibility
The above is tested with 1000 list elements, the page rendering time with content-visibility
is about 37ms, while the page rendering time without content-visibility
is about 269ms, which is a 7 times improvement!
For pages with more list elements, the rendering performance improvement of content-visibility
is more noticeable.
Reflections 🤔
Is it possible to reduce the memory footprint of the page?
Some students have asked whether content-visibility: auto
reduces the amount of memory used by the page. We can see if there is a change in the amount of memory used by the page before and after using it.
We can see how much memory the page is using via chrome at
First of all there is nocontent-visibility: auto
and the page takes up roughly 96.2MB of memory
Then there is the addition ofcontent-visibility: auto
and the page still takes up 96.2MB of memory
That is to say, it does not reduce the size of the memory footprint of the page, these elements are real in the DOM tree and we can access them through JS as well.
Does it affect the loading behavior of scripts?
If we load the script inside an element that has content-visibility: auto
added to it, and the element is in an invisible state, will the script inside the element load correctly?
<!-- ... 第十二个 -->
<div class="visibility_item">
<div class="inner">
<img src="../../../../images/22-11/content-s1.png" alt="">
<script src="./2.js"></script>
</div>
</div>
It is clear that it does not affect the loading behavior of scripts and images, and that scripts are executed normally after reloading. Combining this with the first point above, we can conclude that the use of content-visibility: auto
only affects the rendering of child elements, and the loading of internal static resources is still normal.
But we need to pay attention to the timing of the script, if you want to get the DOM elements, this time the script can only get the DOM elements before it is loaded, and it has nothing to do with whether it has rendered its own DOM or not!
accessibility
Do elements that use content-visibility: auto
and are in the non-visible area exist in the accessible tree?
Here we can see that content-visibility: auto
is off-screen content that is still available in the document object model and therefore in the accessibility tree (unlike visibility: hidden
). This means we can search for and navigate to that content on the page without waiting for it to load or sacrificing rendering performance.
This functionality feature was updated in chrome 90, and in chrome 85-89, off-screen child elements content-visibility: auto
were marked as invisible.
content-visibility is a new feature added to chrome85, so compatibility isn’t very high yet, but it’s a very useful CSS property due to skipped rendering, and if most of our content is off-screen, utilizing this content-visibility
property can make the initial user load faster. I believe the compatibility issue will be solved in the near future~