Author: Desert

When users visit a Web site (page) or application, they expect it to load quickly and function smoothly. If it’s too slow, users are more likely to get impatient and leave your Web site or application. As a developer, providing faster access to your application and providing a great user experience are essential basic skills, and there are many things Web developers can do in their development to improve the user experience. So today we’re going to talk to you about CSS techniques that can help you speed up your Web page rendering.

Visibility content visibility

In general, most Web applications have complex UI elements, and some of the content is outside the viewable area of the device (outside the viewable area of the user’s browser). For example, the red area in the following image is outside the viewable area of the mobile device screen:

In this case, we can use the CSS content-visibility to skip the off-screen rendering of the content. That said, if you have a lot of off-screen Content, this will significantly reduce page rendering time.

This capability is a new CSS feature and is part of the W3C CSS Containment Module Level 2 Module. It is also one of the features that has the biggest impact on improving rendering performance. Content-visibility can accept visible, auto, and hidden values, but we can use content-visibility:auto on an element to directly improve the rendering performance of the page.

Let’s say we have a page like the one below, and the entire page has a list of cards, about 375 of them, about 12 of them in the visible area of the screen. As you can see below, rendering this page took the browser about 1037ms:

You can add Content-visibility to all cards:

.card {
    content-visibility: auto;
}
Copy the code

After adding the Content-Visibility style to all cards, the render time of the page is reduced to 150ms, increasing rendering performance by almost six times:

As you can see, Content-Visibility is very powerful, and it is very useful to improve page rendering. In other words, with the CONTent-visibility property of CSS, it is much easier to influence the browser’s rendering process. Essentially, this property changes the visibility of an element and manages its render state.

Content-visibility is somewhat similar to the DISPLAY and visibility properties of CSS, however, content-Visibility operates differently than these properties.

The key capability of Content-Visibility is that it allows us to defer rendering of HTML elements of our choice. By default, the browser renders all elements in the DOM tree that can be viewed by the user. The user can see all the elements in the viewable area of the window and scroll through other elements on the page. Rendering all the elements at once (including HTML elements that are not visible outside of the viewport) allows the browser to properly calculate the size of the page while maintaining consistency in the overall page layout and scrollbar.

If the browser does not render some elements within the page, scrolling will be a nightmare because the page height cannot be correctly calculated. This is because Content-Visibility treats the height of the element assigned to it as 0, and the browser will change the height of this element to 0 before rendering, thus confusing our page height and scrolling. However, this behavior is overridden if the height has been explicitly set for the element or its children. If you do not have an explicit height set for your element, and do not do so because of the possible side effects of doing so, you can use the contain-intrinsic-size function to ensure that the element is rendered correctly while retaining the benefits of delayed rendering.

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

This also means that it will be laid out like a single child element with a “intrinsic-size”, ensuring that the div (.card in the example) that you didn’t size will still take up space. Contain -intrinsic-size serves as a placeholder size in place of the rendered content.

While the extension-intrinsic-size allows an element to have a placeholder, the scroll bar will still have minor problems if a large number of elements are set to content-visibility: auto.

The other two values visible and hidden provided by content-visibility allow us to implement explicit and hidden values like elements, similar to the switch between display’s None and non-None values:

In this case, Content-Visibility can improve the rendering performance of elements that are frequently displayed or hidden, such as the display and hiding of modal boxes. Content-visibility provides this performance boost thanks to the fact that its hidden value function is different from other values:

  • display: none: Hides an element and breaks its rendered state. This means that unhiding elements is just as expensive as rendering new elements with the same content
  • visibility: hidden: Hides the element and keeps its rendered state. This doesn’t really remove the element from the document, because it (and its subtrees) still occupy geometric space on the page and can still be clicked. It can also update the render state as needed, even if hidden
  • content-visibility: hidden: Hides the element and retains its rendered state. This means that the element behaves and when hiddendisplay: noneSame, but again it costs much less

Extension reading for the Content-Visibility property:

  • content-visibility: the new CSS property that boosts your rendering performance
  • More on content-visibility

Use will-change wisely

The CSS Renderer requires a preparation process before rendering CSS styles, because some CSS properties require a lot of preparation by the CSS Renderer before rendering. This can easily lead to a page that freezes up and gives users a bad experience.

For example, Web animation (moving elements) is usually rendered together with other elements on a regular basis. In the past, IN animation development, CSS 3D transforms (translate3D () or translateZ() in Transform are used to turn on GPU acceleration to make the animation smoother. But doing so is a kind of dark magic that refers the element and its context to another “layer” that is rendered independently of the other elements. But this extraction to a new layer is also relatively expensive, possibly delaying the Transform animation by hundreds of milliseconds.

However, instead of hacks like Transform, I can now turn on GPU acceleration by using the CSS’s will change attribute, which indicates that an element will change a particular attribute, allowing the browser to do the necessary optimization beforehand. That is, will-change is a UA hint that does not have any styling effects on the elements you use it on. But it’s worth noting that if a new cascading context is created, it can produce a look and feel.

Here’s an example of an animation:

<! -- HTML --> <div class="animate"></div> /* CSS */ .animate { will-change: opacity }Copy the code

When the browser renders the code above, it creates a separate layer for the element. It then delegates the rendering of that element to the GPU along with other optimizations, that is, the browser recognizes the will-change attribute and optimizes future changes related to opacity. This will make the animation much smoother as the GPU acceleration takes over the rendering of the animation.

According to the performance benchmark done by @Maximillian Laumeister, he achieved a render speed of over 120FPS with this single line variation, an improvement of about 70FPS compared to the initial render speed of around 50FPS.

The use of will-change is not complicated. It can accept the following values:

  • auto: The default value. The browser optimizes the value based on specific conditions
  • scroll-position: indicates that the developer is going to change the scroll position of the element, for example, browsers usually only render the content in the scroll window of the scrollable element. Some content is beyond the window (not in the browser’s viewable area). ifwill-changeExplicitly setting this value will extend the rendering of the content around the “scroll window” for smoother, longer, faster scrolling (making elements scroll more smoothly)
  • content: indicates that the developer will change the content of an element. For example, browsers often cache most elements that do not change frequently. But if the contents of an element are constantly changing, creating and maintaining this cache is a waste of time. ifwill-changeExplicitly setting this value can reduce the browser’s caching of elements, or avoid caching altogether. To re-render elements from start to finish. Use this value at the very end of the tree as much as possible, because it is applied to the children of the element it declares, and can have a significant impact on page performance if used on higher nodes in the tree
  • <custom-ident>: represents an element attribute that the developer will change. If the given value is an abbreviation, it is extended by default, for example,will-changeThe value ispaddingThat will complete everythingpaddingProperties, such aswill-change: padding-top, padding-right, padding-bottom, padding-left;

For detailed usage, see:

  • CSS Will Change Module Level 1
  • Everything You Need to Know About the CSS will-change Property
  • CSS Reference:will-change

While will-change can improve performance, this attribute should be considered a last resort, not for premature optimization. You should only use it if you have to deal with performance issues. If you abuse it, it can degrade Web performance. Such as:

Use will-change to indicate that the element will change in the future.

Therefore, if you try to use will-change and animation at the same time, it will not give you optimization. Therefore, it is recommended to use will-change on the parent element and animation on the child element.

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

.animate-element {
    transition: opacity .2s linear
}
Copy the code

Do not use non-animated elements.

When you use will-change on an element, the browser will try to optimize it by moving the element to a new layer and converting the work to the interactive GPU. If you don’t have anything to convert, it’s a waste of resources.

In addition, it is not easy to make good use of will-change. MDN has made corresponding description in this aspect:

  • Don’t putwill-changeApplied to too many elements: Browsers have tried to optimize everything they can. There are some more powerful optimizations if withwill-changeIn combination, it can consume a lot of machine resources, which, if overused, can lead to pages that respond slowly or consume a lot of resources. Such as*{will-change: transform, opacity; }
  • Use it sparingly: Typically, when an element is restored to its original state, the browser will throw away any previous optimizations. But if you declare it explicitly in the stylesheetwill-changeAttribute, indicating that the target element may change frequently, and the browser will save the optimization work longer than before. So the best practice is to switch through scripts when elements change before and afterwill-changeThe value of the
  • Don’t Apply too earlywill-changeTo optimize theIf your page has no performance problems, don’t addwill-changeAttribute to extract a little bit of speed.will-changeIs originally designed as a last resort optimization to try to solve existing performance problems. It should not be used to prevent performance problems. Excessive use ofwill-changeThis can lead to a large memory footprint and a more complex rendering process as the browser tries to prepare for possible changes in the process. This can lead to more serious performance problems.
  • Give it enough time to workThis property is used to let the page developer tell the browser which properties may change. The browser can then choose to do some optimization before the change occurs. So it’s important to give the browser time to actually do this optimization work. When you use it, you need to try to find some way to know a certain amount of time in advance that the element is likely to change, and then add to itwill-changeProperties.

As a final note, it is recommended that the element’s will-change be removed after all animation is complete. The following example shows an example of how to use a script to apply the will-change property correctly, as you should in most scenarios.

var el = document.getElementById('element'); El.addeventlistener (' mouseEnter ', hintBrowser); // Set the will-change attribute for the element when the mouse moves over it; El.addeventlistener ('animationEnd', removeHint); // Remove the will-change attribute when the CSS animation ends. Function hintBrowser() {this.style. WillChange = 'transform, opacity'; function hintBrowser() {this.style. WillChange = 'transform, opacity'; } function removeHint() { this.style.willChange = 'auto'; }Copy the code

In practice, will-change can be denoted as the following rules: five can be done, three cannot be done:

  • Rarely used in style sheetswill-change
  • towill-changeEnough time for it to do its job
  • use<custom-ident>To respond to super-specific changes (e.g.,left, opacityEtc.)
  • You can use it in JavaScript (add and remove) if you want
  • After the modification is complete, delete itwill-change
  • Do not declare too many properties at once
  • Don’t apply it to too many elements
  • Don’t waste resources on elements that have stopped changing

Contain elements and their contents as separate as possible from the rest of the document tree

The W3C CSS Containment Module Level 2 provides another attribute, in addition to the previously described Content-visibility attribute, namely, Contain. This property allows us to specify a specific DOM element and its child elements that are independent of the overall DOM tree structure. The goal is to give the browser the ability to redraw and rearrange only some elements, rather than the entire page at a time. That is, it allows the browser to recalculate layout, style, drawing, size, or any combination of them for a limited area of the DOM rather than the entire page.

Contain specifies how the element is independent of the document tree by setting it to one of the following values:

  • Layout: This value indicates that the internal layout of the element is unaffected by any external influence, and that the element and its contents are unaffected by the upper layer
  • Paint: This value indicates that children of an element cannot be displayed outside the scope of the element, and that the element will not overflow (or, if it does, will not be displayed).
  • Size: This value indicates that the size of an element’s box is independent of its contents, that is, its children are ignored when calculating the size of the element’s box
  • content: the value iscontain: layout paintThe shorthand
  • strict: the value iscontain: layout paint sizeThe shorthand

Of these values, size, Layout, and paint can be used alone or in combination. In addition, content and strict are combined values, i.e. content is a combination of Layout paint and strict is a combination of Layout paint size.

Contain size, Layout, and paint provide different ways to influence browser rendering calculations:

  • size: tells the browser that the container should not cause the position on the page to move when its content changes
  • layout: tells the browser that descendants of a container should not cause the layout of elements outside its container to change, and vice versa
  • paint: Tells the browser that the contents of the container will never be drawn beyond the size of the container, and if the container is blurry, the content will never be drawn at all

@Manuel Rego Casasnovas provides an example of how presentation can improve Web page rendering. In this example, there are 10,000 DOM elements like the following:

<div class="item"> <div>Lorem ipsum... </div> </div>Copy the code

Use JavaScript’s textContent API to dynamically change the contents of div. Item > div:

const NUM_ITEMS = 10000; const NUM_REPETITIONS = 10; function log(text) { let log = document.getElementById("log"); log.textContent += text; } function changeTargetContent() { log("Change \"targetInner\" content..." ); // Force layout. document.body.offsetLeft; let start = window.performance.now(); let targetInner = document.getElementById("targetInner"); targetInner.textContent = targetInner.textContent == "Hello World!" ? "BYE" : "Hello World!" ; // Force layout. document.body.offsetLeft; let end = window.performance.now(); let time = window.performance.now() - start; log(" Time (ms): " + time + "\n"); return time; } function setup() { for (let i = 0; i < NUM_ITEMS; i++) { let item = document.createElement("div"); item.classList.add("item"); let inner = document.createElement("div"); inner.style.backgroundColor = "#" + Math.random().toString(16).slice(-6); inner.textContent = "Lorem ipsum..." ; item.appendChild(inner); wrapper.appendChild(item); }}Copy the code

Without contain, even if the changes are on a single element, the browser’s rendering of the layout will take a lot of time because it traverses the entire DOM tree (in this case, the DOM tree is huge because it has 10,000 DOM elements) :

In this case, the size of the div is fixed, and what we change in the internal div will not overflow it. Therefore, we can apply contain: strict to a project so that when changes occur inside the project, the browser does not need to access other nodes; it can stop checking the content on that element and avoid going outside.

Although each item in this example is simple, the Web performance has changed dramatically by using Contain, from ~4ms to ~0.04ms, which is a huge difference. Imagine what would happen if the DOM tree had a very complex structure and content, but only a small part of the page was modified, and if it could be isolated from the rest of the page?

There is more about The contain:

  • Let’s Take a Deep Dive Into the CSS Contain Property
  • Helping Browsers Optimize With The CSS Contain Property
  • CSS contain Property

Use font-display to resolve layout offsets due to fonts (FOUT)

In the process of Web development, it is inevitable to use @font-face technology to reference special fonts (fonts not available in the system), and it is possible to use more personalized fonts with variable font features.

Using @font-face to load a font strategy might look something like this:

The above image is from @Zachleat’s A COMPREHENSIVE GUIDE TO FONT LOADING STRATEGIES.

When using non-system fonts on the Web (the ones introduced by the @font-face rule), the browser may not get the Web font in time, so it will render it with a backup system font and then optimize our font. At this point, it is easy to cause Unstyled text to flash, and the entire layout will appear to be FOUT.

Fortunately, according to the @font-face rule, the font-display property defines how the browser loads and displays the font file, allowing the text to display the fallback font if the font load or fails. You can improve performance by relying on compromised unstyled text flashes to make text visible instead of white screens.

The border-display property of CSS has five different values:

  • auto: The default value. Typical browser font loading occurs, where the text in a custom font is hidden until the font is loaded. The font display policy is consistent with the browser. Currently, most browsers have a similar default policyblock
  • block: Gives fonts a short blocking time (recommended in most cases)3s) and an infinite amount of exchange time. In other words, if the font is not loaded, the browser will first draw “invisible” text; Switch fonts as soon as the fonts are loaded. To do this, the browser creates an anonymous font of type similar to the selected font, but with no “ink” in any of the glyphs. The page is not available until the text is rendered with a specific font, and only then should it be usedblock.
  • swapUse:swap, then the blocking phase time is0, the time of the exchange phase is infinite. That is, if the font hasn’t finished loading, the browser will immediately draw the text and switch the font once it has loaded. Similar to blocks, use only if rendering text with a particular font is important to the page and rendering with another font will still display the correct informationswap.
  • fallbackA: It could beautoandswapIs a compromise. Text that needs to be rendered with a custom font will not be visible for a short time. If the custom font is not finished loading, then the unstyled text will be loaded first. Once the custom font is loaded, the text is styled correctly. usefallbackThe blocking phase time will be very small (less than is recommended in most cases)100ms), and the switching phase is short (recommended in most cases3s). In other words, if the font is not loaded, the backup font rendering will be used first. Once the load is successful, the font is switched. But if you wait too long, the page will always use the backup font. If you want the user to start reading as soon as possible without disrupting the user experience by changing the text style as a new font is loaded,fallbackIs a good choice.
  • optional: the effect andfallbackAlmost the same, first the text is invisible for a very short time, and then the unstyled text is loaded. However,optionalThe option lets the browser decide whether to use a custom font, a decision that depends largely on the browser’s connection speed. If it is slow, your custom font may not be used. useoptionalThe blocking phase time is very small (less than recommended in most cases)100ms), the time of the exchange phase is0.

Here is an example of using the swap value:

@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;
}
Copy the code

In this example we abbreviate the font by using only the WOFF2 file. In addition, we use swap as the value of font-display, and the loading situation of the page will be as shown in the figure below:

Note that font-display is generally used within the @font-face rule.

For more information on font loading and font-display, read:

  • A deep dive into webfonts
  • How to avoid layout shifts caused by web fonts
  • The Best Font Loading Strategies and How to Execute Them
  • A font-display setting for slow connections
  • How to Load Fonts in a Way That Fights FOUT and Makes Lighthouse Happy
  • The importance of @font-face source order when used with preload
  • The Fastest Google Fonts
  • A COMPREHENSIVE GUIDE TO FONT LOADING STRATEGIES

Scroll-behavior makes scrolling smoother

Earlier in scroll Features and New Features of Scroll that change the user experience, we introduced several scroll features that can be used to change the user experience, such as scroll capture, overscroll-behavior, and scroll-behavior.

Scroll-behavior is a new feature provided by CSSOM View Module, which can easily help us achieve the silky scrolling effect. This property specifies the scrolling behavior for a scroll box. Any other scrollings, such as those caused by user behavior, are not affected by this property.

Scroll-behavior accepts two values:

  • Auto: The scroll box is immediately rolled
  • Smooth: The scroll box uses defined time functions over a period of time defined by the user agent to achieve smooth scrolling. The user agent platform should follow the convention, if any

In addition, there are three global values: inherit, initial, and unset.

It’s easy to use, just use scroll-behavior:smooth on this element. Therefore, in many cases to make the page scroll smoother, it is recommended to set a style in the HTML directly like this:

html {
    scroll-behavior:smooth;
}
Copy the code

You’ll feel better if you compare the results:

For more information on the Scrollbehavior property, take a moment to read the following articles:

  • CSSOM View Module:scroll-behavior
  • CSS-Tricks: scroll-behavior
  • Native Smooth Scroll behavior
  • PAGE SCROLLING IN VANILLA JAVASCRIPT
  • smooth scroll behavior polyfill

Enable GPU render animation

The browser is optimized for handling CSS animations and animation properties that do not trigger rearrangements (and therefore draw) well. To improve performance, you can move the animate nodes from the main thread to the GPU. Properties that will result in synthesis include 3D Transforms (Transform: translateZ(), Rotate3D (), etc.), Animating, Transform, and Opacity, position: Fixed, will-change, and filter. Some elements, such as

Reduced render blocking time

Today, many Web applications must meet a variety of requirements, including PC, tablet, and mobile. To achieve this responsive nature, we must write new styles based on the media size. When it comes to page rendering, it cannot start the render phase until the CSS Object Model (CSSOM) is ready. Depending on your Web application, you may have a large style sheet to satisfy the form factor for all devices.

However, suppose we split it into multiple stylesheets based on form factors. In this case, we can just let the main CSS file block the critical path and download it with high priority, while the other stylesheets are downloaded with low priority.

<link rel="stylesheet" href="styles.css">
Copy the code

After breaking it up into multiple stylesheets:

<! -- 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" />Copy the code

By default, the browser assumes that each specified style sheet blocks rendering. Append the media query by adding the media attribute to tell the browser when to apply the style sheet. When the browser sees a style sheet that it knows will only be used for a particular scene, it will still download the style, but it will not block rendering. By splitting the CSS into multiple files, the size of the main blocking render file (in this case, styles.css) becomes smaller, thus reducing the amount of time the render is blocked.

Avoid @import containing multiple stylesheets

With @import, we can include a stylesheet within another stylesheet. When we’re working on a large project, using @import makes the code more concise.

The key fact about @import is that it is a blocking call because it has to get the file through the network request, parse the file, and include it in the stylesheet. If we nested @import in the stylesheet, rendering performance would be impeded.

/* style.css */
@import url("windows.css");

/* windows.css */
@import url("componenets.css");
Copy the code

Compared to using @import, we can do the same thing with multiple links, but the performance is much better because it allows us to load the stylesheet in parallel.

Notice The mode of dynamically modifying custom attributes

CSS custom properties, also known as CSS variables, are very mature features that can be boldly used in Web development:

:root { --color: red; }

button {
    color: var(--color);
}
Copy the code

When using CSS custom attributes, it is common to register a custom attribute at the root (root element). The custom attribute registered in this way is a global custom attribute (global variable) that can be inherited by all nested child elements. In the example above, the –color property allows any Button style to use it as a variable.

If you are familiar with CSS custom properties, you can use style.setProperty to reset the value of a custom property that has already been registered. However, you need to be careful when modifying the root custom attribute because it can affect Web performance. Back in 2017 @Lisi Linhart explained this in Performance of CSS Variables.

  • When using CSS variables, we should always be aware of the scope in which our variable is defined. If we change it, it will affect many children, resulting in a large number of style recalculations.
  • Use in conjunction with CSS variablescalc()Is a good way to get more flexibility and limit the number of variables we need to define. Test in different browserscalc()In combination with the CSS variables, no major performance issues were found. However, there is limited support for some units in some browsers, such asdegormsSo we have to keep this in mind.
  • If we compare setting variables with inline styles in JavaScriptsetPropertyMethod performance flags, there are some noticeable differences between browsers. Setting properties with inline styles is very fast in Safari and very slow in Firefox, so usesetPropertySetting variables is preferred

The details of this will not be explained here, but if you are interested in this area, you can read the following articles:

  • Performance of CSS Variables
  • Control CSS loading with custom properties
  • CSS Custom Properties performance in 2018
  • Improving CSS Custom Properties performance

summary

Many people may say that with the arrival of 5G, the performance of terminal devices is getting better and better, and the network environment is getting stronger and stronger, so Web performance is no longer an issue, but in fact, performance is always inevitable in the process of Web development. And one of our essential technologies is to provide a smoother experience for our users. There are many ways and means to optimize Web performance today, but paying attention to every detail of development can make it even better. As mentioned in the article.

Beyond the points mentioned in this article, there are other ways to use CSS to improve the performance of your web pages. Of course, some of the features mentioned in this article, such as Content-visibility, Contain, etc., are not yet supported by all browsers, but they will certainly lead to faster page rendering in the future. In addition, some of the techniques mentioned in this article, such as CSS reference methods, CSS blocking, etc., are not covered in depth. If you’re interested, keep an eye out for further updates; If you have better advice or experience in this area, please share it with us in the comments below.