Desmond

Desmond

An introvert who loves web programming, graphic design and guitar
github
bilibili
twitter

CSS Rendering Performance Optimization

content-visibility#

Improve initial load time by skipping the rendering of offscreen content.

Typically, many websites have complex graphical interfaces on their pages, and some content may be outside the user's browser viewport. In such cases, the content-visibility property in CSS can be used to skip the rendering of offscreen content, thereby reducing the rendering time of the page. This feature is a new addition in the latest version of CSS and has a significant impact on improving rendering performance. The content-visibility property can have three values: visible, auto, and hidden. However, we can generally improve the rendering performance of the page by setting content-visibility to auto, especially when there is a lot of offscreen content on the page. Essentially, this property changes the visibility of an element and manages its rendering state.

The main function of content-visibility is to allow us to postpone the rendering of HTML elements. By default, the browser renders all visible elements, including HTML elements that are not visible outside the viewport. This allows the browser to correctly calculate the page size and maintain consistency in the overall page layout and scrollbars. If not all elements are rendered, scrolling can become chaotic because the browser cannot calculate the page height.

However, content-visibility treats the height of the elements assigned to it as 0, setting their height to 0 before rendering. This can lead to confusion in page height and scrollbars. However, if a height has already been explicitly set for the element or its child elements, this issue will not occur. In the absence of an explicitly set height, contain-intrinsic-size can be used to ensure that the element is rendered correctly while retaining the benefits of delayed rendering.

.card {
    content-visibility: auto;
    contain-intrinsic-size: 200px;
}

By using contain-intrinsic-size, we can ensure that a div without set dimensions (e.g., .card) still occupies space and lays out like a single child element with intrinsic size. This property acts as a placeholder to replace the rendered content.

While using content-visibility: auto can reduce page rendering time, if many elements are set with this property, scrollbar issues may still arise.

In addition to content-visibility: auto, content-visibility also provides two other values: visible and hidden. This allows us to toggle between explicit and hidden elements, similar to switching between display: none and non-none values.

In this case, content-visibility can improve the rendering performance of elements that are frequently shown or hidden, such as the display and hiding of modals. content-visibility can provide this performance boost due to the unique functionality of its hidden value (hidden) compared to other values:

  • display: none: Hides the element and disrupts its rendering state. This means that unhiding the element is as costly as rendering a new element with the same content.

  • visibility: hidden: Hides the element while maintaining its rendering state. This does not actually remove the element from the document, as it (and its subtree) still occupies geometric space on the page and can still be clicked. It can also update the rendering state whenever needed, even while hidden.

  • content-visibility: hidden: Hides the element while retaining its rendering state. This means that the element behaves like display: none when hidden, but the cost of showing it again is much lower.

References:
https://web.dev/content-visibility/

will-change#

Before rendering CSS styles, the CSS renderer needs to prepare, as certain CSS properties require significant preparation to achieve rendering. This can lead to page stuttering, resulting in a poor user experience.

For example, animations on web pages often require regular rendering, including dynamic elements and others. The traditional approach is to use CSS 3D transforms (like translate3d() or translateZ()) to enable GPU acceleration, making animations smoother. However, this method is costly and can lead to animation delays of hundreds of milliseconds.

Now, the will-change property in CSS can be used to directly enable GPU acceleration without using hacks like transform. This property indicates to the browser that specific properties will be modified, allowing for necessary optimizations. This means that will-change is a hint that does not have any stylistic impact on the element using it. However, it is important to note that creating a new stacking context may have visual effects.

When the browser renders an element with will-change, it creates a separate layer for that element. It then delegates the rendering of that element to the GPU along with other optimizations, meaning the browser recognizes the will-change property and optimizes future changes related to opacity. This makes animations smoother as GPU acceleration takes over the rendering of the animation.

The use of will-change is not complicated, and it accepts the following values:

  • auto: The default value, where the browser optimizes based on specific situations.

  • scroll-position: Indicates that the developer will change the scroll position of the element. For example, the browser typically only renders content within the "scroll window" of scrollable elements. If some content exceeds that window (not in the browser's visible area), explicitly setting this value will extend the rendering of content around the "scroll window," allowing for smoother and faster scrolling.

  • contents: Indicates that the developer will change the content of the element. The browser often caches most elements that do not change frequently. However, if an element's content is constantly changing, generating and maintaining this cache is a waste of time. If will-change explicitly sets this value, it can reduce the browser's caching of the element or completely avoid caching, resulting in the element being re-rendered from start to finish. When using this value, it is best to apply it at the end of the document tree, as this value will be applied to the child nodes of the declared element. Using it at higher nodes in the document tree may significantly impact page performance.

  • <custom-ident>: Indicates the properties of the element that the developer will change. If the given value is a shorthand, it is expanded by default. For example, if the value set for will-change is padding, it will expand to all padding properties, such as will-change: padding-top, padding-right, padding-bottom, padding-left;.

Using will-change indicates that the element will change in the future#

Therefore, if you try to use will-change and animations simultaneously, it will not provide you with optimizations. It is recommended to use will-change on the parent element and animations on the child elements.

.animate-element-parent {
    will-change: opacity;
}

.animate-element {
    transition: opacity .2s linear;
}

Do not use on non-animated elements#

When using will-change on an element, the browser will attempt to move that element to a new layer and optimize it through GPU transformations. However, if there is nothing to transform, it can lead to resource waste.

Additionally, using will-change requires careful handling. The MDN website provides relevant descriptions:

  • Do not apply will-change to too many elements: The browser has already tried to optimize everything that can be optimized. Some more powerful optimization measures may be combined with will-change, which can consume a lot of machine resources. Overusing it may lead to slow page responses or high resource consumption. For example, * { will-change: transform, opacity; }.

  • Use it moderately: Generally, when an element returns to its initial state, the browser will abandon previous optimizations. However, if the will-change property is explicitly declared in the stylesheet, it indicates that the target element may change frequently, so the browser will retain the optimization work for a longer time than before. The best practice is to toggle the value of will-change through scripts before and after the element changes.

  • Do not apply will-change optimizations too early: If the page performs well in terms of performance, the will-change property should not be added to pursue minor speed improvements. The design intent of will-change is to address existing performance issues rather than prevent them. Overusing will-change can lead to high memory usage and complicate the rendering process as the browser tries to prepare for potential changes. This can lead to more severe performance issues.

  • Give it enough time to work: This property is used to inform the browser which properties may change. The browser can then attempt to make some optimizations before the changes occur. Therefore, it is crucial to give the browser enough time to genuinely perform these optimizations. When using it, find ways to anticipate potential changes to the element and add the will-change property accordingly.

Finally, it is important to note that it is recommended to remove the will-change property from elements after all animations are completed. The following example demonstrates how to correctly apply the will-change property using scripts, which you should generally do in most scenarios.

var el = document.getElementById('element');

// Set will-change property when the mouse enters the element
el.addEventListener('mouseenter', hintBrowser);
// Clear will-change property after the CSS animation ends
el.addEventListener('animationEnd', removeHint);

function hintBrowser() {
    // Fill in the CSS property names you know will change during the CSS animation
    this.style.willChange = 'transform, opacity';
}

function removeHint() {
    this.style.willChange = 'auto';
}

Let elements and their content be as independent as possible from the rest of the document tree (contain)#

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.

The W3C CSS Containment Module Level 2 provides another property contain, in addition to the previously introduced content-visibility property. This property allows us to specify specific DOM elements and their child elements so that they can operate independently of the entire DOM tree structure. The goal is to enable the browser to repaint and reflow only specific elements without having to operate on the entire page each time. In other words, contain allows the browser to recalculate layout, style, painting, size, or any combination of these, targeting a limited area of the DOM rather than the entire page.

In practical use, we can set one of the five values below using the contain property to specify how the element operates independently of the document tree:

  • layout: This value indicates that the internal layout of the element is not affected by anything external, and the element and its content will not affect higher levels.
  • paint: This value indicates that the child elements of the element cannot be displayed outside the scope of that element, meaning that the element will not have any content overflow (or even if it overflows, it will not be displayed).
  • size: This value indicates that the size of the element's box is independent of its content, meaning that when calculating the size of the element's box, its child elements will be ignored.
  • content: This value is shorthand for contain: layout paint.
  • strict: This value is shorthand for contain: layout paint size.

The size, layout, and paint values of contain provide different ways to influence the browser's rendering calculations:

  • size: Tells the browser that when its content changes, the container should not cause position shifts on the page.
  • layout: Tells the browser that the descendants of the container should not cause layout changes to elements outside of its container, and vice versa.
  • paint: Tells the browser that the content of the container will never be drawn outside the dimensions of the container. If the container is blurred, the content will not be drawn at all.

image

References:

Use font-display to solve layout shifts caused by fonts (FOUT)#

In the web, when using non-system fonts (fonts introduced via the @font-face rule), the browser may not be able to retrieve the web font in time, leading to the use of fallback system fonts to render text. This can cause unstyled text to flash and shift the entire page layout (FOUT).

Fortunately, the CSS font-display property defines how the browser loads and displays font files, allowing text to display fallback fonts when the font is loading or fails to load. This can improve performance by compromising on unstyled text flashing to make text visible instead of waiting on a white screen.

The CSS font-display property has five values:

  • auto: The default value. Text using the custom font will be hidden until the font has finished loading, similar to the default strategy of most browsers.
  • block: Gives the font a short blocking time and an infinite swap time, rendering "invisible" text until the font has loaded; once the font has loaded, it immediately switches the font. This should only be used when rendering text with a specific font is crucial to the page.
  • swap: The blocking time is 0, and the swap time is infinite, immediately rendering text before the font has loaded; once the font has successfully loaded, it immediately switches the font. This should only be used when rendering text with a specific font is crucial to the page, and rendering with other fonts will still display the correct information.
  • fallback: For a short time, text that needs to be rendered with the custom font is invisible; if the font has not finished loading, unstyled text is rendered first. Once the font has loaded successfully, the text will be styled correctly. If the wait time is too long, the page will continue to use the fallback font. If you want users to start reading as soon as possible without interference from text style changes due to new font loading, fallback is a good choice.
  • optional: Similar to fallback, text is invisible for a very short time before loading unstyled text. However, the optional option allows the browser to decide whether to use the custom font based on the browser's connection speed. If the speed is slow, the custom font may not be used. When using optional, the blocking time should be very small, and the swap time should be 0.

image

@font-face {
    font-family: "Open Sans Regular";
    font-weight: 400;
    font-style: normal;
    src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2");
    font-display: swap;
}

References:

scroll-behavior#

scroll-behavior is a new feature provided by the CSSOM View Module that helps us achieve smooth scrolling effects. This property can specify the scrolling behavior for a scroll box without affecting any other scrolling generated by user actions.

scroll-behavior has two values:

  • auto: Indicates that the scroll box scrolls immediately.
  • smooth: Indicates that the scroll box scrolls smoothly over a user-agent-defined period using a defined timing function. Note that if present, the user-agent platform should follow the convention.

Enable GPU rendering for animations#

Browsers optimize for handling CSS animations and properties that do not trigger reflows (and thus also cause repaints). To improve performance, animated nodes can be moved from the main thread to the GPU. Properties that lead to compositing include 3D transforms (transform: translateZ(), rotate3d(), etc.), animating, transform and opacity, position: fixed, will-change, and filter. Some elements, such as <video>, <canvas>, and <iframe>, are also on their respective layers. When elevating an element to a layer (also known as compositing), animation transformation properties will be completed in the GPU, improving performance, especially on mobile devices.

Reduce render-blocking time#

Break large stylesheets into multiple stylesheets, allowing only the main CSS file to block the critical path and download it with high priority, while letting other stylesheets download with low priority.

<!-- style.css contains only the minimal styles needed for the page rendering -->
<link rel="stylesheet" href="styles.css" media="all" />

<!-- Following stylesheets have only the styles necessary for the form factor -->
<link rel="stylesheet" href="sm.css" media="(min-width: 20em)" />
<link rel="stylesheet" href="md.css" media="(min-width: 64em)" />
<link rel="stylesheet" href="lg.css" media="(min-width: 90em)" />
<link rel="stylesheet" href="ex.css" media="(min-width: 120em)" />
<link rel="stylesheet" href="print.css" media="print" />

By default, the browser assumes that each specified stylesheet is render-blocking. By adding the media attribute with additional media queries, you inform the browser when to apply the stylesheet. When the browser sees a stylesheet that it knows will only be used in specific scenarios, it will still download the stylesheet but will not block rendering. By splitting CSS into multiple files, the size of the main render-blocking file (in this case, styles.css) becomes smaller, thereby reducing the time rendering is blocked.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.