We all know that for the website, performance is crucial, CSS as page rendering and content presentation of an important link, affecting the user’s first experience of the entire site. Therefore, the performance optimization related to it cannot be ignored.
We often think about performance optimization only when the project is completed, and it is often postponed until the end of the project, or even until serious performance problems are exposed, which I believe most people know very well.
In the author’s opinion, in order to avoid this situation more, first of all, it is necessary to pay attention to the work related to performance optimization, which is carried out throughout the entire product design and development. Second, it is to understand the performance-related content, and naturally carry out performance optimization in the process of project development. Finally, and most importantly, start implementing optimization now.
This article will introduce CSS performance optimization techniques , I will divide them into two categories of practical and recommended , a total of 8 tips. Practical tips can be quickly applied in the project, can be very good to improve performance, is also often used by the author, it is recommended that you practice in the project as soon as possible. Suggested tips, some of which may not have a significant impact on performance, and some of the usual people will not be so used, so I will not focus on the story, readers can understand according to their own situation can be.
Before the official start, you need to have some understanding of the workings of the browser 2 , the need for partners can be a simple understanding of the first.
Below we begin with 4 optimization tips for the practice type, starting with the first screen key CSS.
1. Inline first-screen critical CSS (Critical CSS)
Performance optimization has an important indicator – the first effective drawing (First Meaningful Paint, referred to as FMP) that is, the page’s primary content (primary content) appears on the screen time. This indicator affects the user to see the page before the time required to wait, and ** inline first screen critical CSS (i.e., Critical CSS, can be called the first screen critical CSS) ** can reduce this time.
Everyone should be used to referencing external CSS files via the link tag. However, it is important to know that inlining CSS directly into the HTML document makes the CSS download faster. When using external CSS files, you need to know which CSS files to reference after the HTML document has finished downloading before you download them. So inlining CSS enables the browser to start page rendering earlier, because it can render after the HTML has finished downloading.
Since inlining CSS makes it possible to advance the start of page rendering, is it possible to inline all CSS? The answer is clearly no. This approach does not work for inlining larger CSS files. Because of the limitations of the initial congestion window at 3 (a TCP-related concept, usually 14.6kB in compressed size), inlining CSS files beyond this limit will require more round-trips between the server and the browser, which will not advance the page rendering time. Therefore, we should inline only the key CSS needed to render the first screen content into the HTML.
Now that we know that inlining first-screen critical CSS can optimize performance, the next step is how to determine first-screen critical CSS. Obviously, we do not need to manually determine what content is the first screen critical CSS. Github has a project Critical CSS 4 , can be the first screen belongs to the critical style extraction, you can look at the project, combined with their own build tools to use. Of course, in order to ensure the correctness, you’d better check the extracted content is not missing.
There is one downside to inlining CSS though, the CSS is not cached after inlining and is re-downloaded each time. But as mentioned above, this doesn’t seem to be a big problem if we keep the file size after inlining under 14.6kb.
As above, we have covered why and how to inline key CSS, so what do we do with the remaining CSS? It is recommended to use external CSS to bring in the remaining CSS so that caching can be enabled, in addition to loading them asynchronously.
2. Asynchronous loading of CSS
CSS blocks rendering and the browser will not render any processed content until the CSS file has been requested, downloaded, and parsed. Sometimes this blocking is necessary because we don’t want the browser to start rendering the page before the required CSS is loaded. Then after inlining the key CSS for the first screen, blocking rendering of the remaining CSS content is not necessary, and external CSS can be used and loaded asynchronously.
So how do you achieve asynchronous loading of CSS? There are four ways to achieve asynchronous loading of CSS in the browser.
The first way is to use JavaScript to dynamically create the stylesheet link element and insert it into the DOM.
const myCSS = document.createElement( "link" );
myCSS.rel = "stylesheet";
myCSS.href = "mystyles.css";
document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );
The second way is to set the media
attribute of the link element to a media type (or media query) that the user’s browser doesn’t match, such as media="print"
, or even a completely non-existent type media="noexist"
. For the browser, if the stylesheet does not apply to the current media type, its priority is lowered and it will be downloaded again without blocking the page rendering.
Of course, this is only done to enable asynchronous loading of CSS, and don’t forget to set the value of media
to screen
or all
once the file has finished loading so that the browser can start parsing the CSS.
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
Similar to the second way, we can also mark the link
element as a alternate
optional stylesheet via the rel
attribute, which also enables asynchronous loading by the browser. Again, don’t forget to change rel
back once it’s loaded.
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
All three of these methods are older. Now, the web standard rel=”preload” 5 indicates how to load resources asynchronously, including CSS-like resources.
<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
Note that as
is required. Ignoring the as
attribute, or incorrectly using the as
attribute will make preload
equivalent to a XHR
request, and the browser will not know what is being loaded, so this type of resource will be loaded with very low priority. as
The optional values for the attribute can be found in the standards documentation above.
It seems that the usage of rel="preload"
is not much different from the above two, both of them change some properties to make the browser load the CSS file asynchronously but do not parse it until the loading is finished and the changes are restored, and then the parsing will start.
But there is actually one important difference between them, and that is that using preload will start loading CSS earlier than using the mismatched media
method, so even though support for this standard is not perfect, it is still recommended that this method be used in preference.
The standard is now a candidate standard and it is believed that browsers will gradually support it. The level of support in each browser is shown in the following chart.
As you can see from the image above this method is not well supported in today’s browsers, but we can do polyfill with loadCSS 6 , so it’s not a big deal if it’s supported or not.
3. Document compression
One of the easiest to think of and most commonly used methods for performance optimization is file compression, a solution that is often highly effective.
The size of the file directly affects the loading speed of the browser, which is especially obvious when the network is poor. I believe that we have long been accustomed to CSS compression, now build tools, such as webpack, gulp/grunt, rollup, etc. also support CSS compression. The compressed file can be significantly reduced, can greatly reduce the browser load time.
4. Remove useless CSS
Although file compression can reduce file size. However, CSS file compression usually only removes useless spaces, which limits the percentage of CSS files that can be compressed. So are there other means to streamline CSS? The answer is obviously yes, if the compressed file still exceeds the expected size, we can try to find and remove useless CSS in the code.
In general, there will be these two kinds of useless CSS code: one is the duplication of code in different elements or other cases, and the other is the CSS code that is not effective within the whole page. For the former, when writing the code, we should extract the public classes as much as possible to minimize duplication. For the latter, in the process of code maintenance by different developers, there will always be CSS code that is no longer in use, and of course this problem may also occur when a person writes. And this useless CSS code not only increases browser downloads, but also increases browser parsing time, which is a big drain on performance. So we need to find and remove this useless code.
Of course, it would be inefficient to remove this useless CSS manually. We can do this with the help of the Uncss 7 library. Uncss can be used to remove useless CSS from stylesheets and supports multiple files and JavaScript injected CSS.
We’ve already talked about the 4 optimization tips for the practice type, and we’ll cover the 4 tips for the advice type below.
1. Selective use of selectors
Most of you should know that CSS selectors are matched from right to left, and this strategy leads to performance differences between different kinds of selectors. Compared to #markdown-content-h3
, it is clear that when using #markdown .content h3
, the browser spends more time generating the render-tree. .content
This is because the latter needs to first find all h3
elements in the DOM, then filter out those whose ancestors are not .content
, and finally filter out those whose ancestors are not #markdown
. Imagine if there are more levels of nesting and more elements in the page, then the matching will naturally be more costly in terms of time.
However, modern browsers have done a lot of optimization in this area, and the difference in performance between different selectors is not significant, or even minimal. In addition, the performance of different selectors in different browsers 8 is not entirely uniform, and it is not possible to account for each browser when writing CSS. For these two reasons, we only need to keep the following in mind when using selectors, the rest can be left to preference.
- Keep it simple and don’t use overly complex selectors with too much nesting.
Wildcard and attribute selectors are the least efficient and require the most elements to match, so try to avoid them.
Don’t use class selectors and ID selectors to modify element labels, such ash3#markdown-content
, which is redundant and inefficient.- Don’t give up readability and maintainability for speed.
If you still have questions about the above points, I recommend that you choose one of the following CSS methodology (BEM 9 , OOCSS 10 , SUIT 11 , SMACSS 12 , ITCSS 13 , Enduring CSS 14 , etc.) as the CSS writing specification. The use of unified methodology can help you form a unified style, reduce naming conflicts, but also to avoid the above problems, in short, the benefits are many, if you have not yet used, use it quickly.
Tips: Why do CSS selectors match from right to left?
More selectors in CSS don’t match, so when thinking about performance, one thing to consider is how to improve efficiency when selectors don’t match. Right-to-left matching is designed to do just that, by making CSS selectors more efficient when they don’t match. So it makes sense that it would take a bit more performance to match.
2. Reducing the use of expensive attributes
When the browser draws the screen, all attributes that require manipulation or computation by the browser are relatively more costly. They reduce the rendering performance of the browser when page redrawing occurs. So while writing CSS, we should minimize the use of expensive properties like box-shadow
/ border-radius
/ filter
/ transparency / :nth-child
etc.
Of course, it’s not to discourage people from using these attributes, as they should all be attributes that we use regularly. The reason for mentioning this is to give people an idea about this. When there are two options to choose from, you can prioritize the option with no expensive attributes or fewer expensive attributes, and if you choose like this every time, the performance of your website will improve somewhat without you realizing it.
3. Optimizing rearrangement and redrawing
During the use of a website, certain actions cause changes in style, which the browser needs to detect and re-render, and some of these actions consume more performance. We all know that a website is only smooth when the FPS is 60. This means that we need to complete all the operations related to each rendering in 16.67ms, so we need to minimize the operations that consume more.
3.1 Reduction of rearrangements
Rearrangement causes the browser to recalculate the entire document and reconstruct the rendering tree, a process that slows down the browser’s rendering speed. As you can see below, there are a number of operations that trigger reordering, and we should avoid triggering them frequently.
- Changes to
font-size
andfont-family
- Changing the inner and outer margins of an element
- Changing CSS Classes via JS
Get position-related attributes (such as width/height/left, etc.) of DOM elements via JS- CSS pseudo-class activation
- Scroll the scrollbar or change the window size
Additionally, we can query which properties trigger reordering and repainting via the CSS Trigger 15 .
It is worth mentioning that certain CSS properties have better reordering performance. For example, when using Flex
, it is faster to rearrange than when using inline-block
and float
, so you can prioritize Flex
in your layout.
3.2 Avoid unnecessary repainting
Redrawing is triggered when the appearance of an element (e.g. color, background, visibility, etc. attributes) changes. Redrawing is unavoidable during the use of a website. However, browsers are optimized to combine multiple rearrangements and redraws into a single execution. However, we still need to avoid unnecessary repainting, such as page scrolling when the trigger hover event, you can scroll in the time to disable the hover event, so that the page in the scrolling will be more fluent.
In addition, we are writing more and more animation-related code in CSS, and we have become accustomed to using animation to enhance user experience. We should also refer to the above when writing animations to minimize the trigger of redrawing and rearranging. In addition, we can also use hardware acceleration 16 and will-change 17 to improve the performance of animation, this article will not expand the details of this, interested partners can click the link to view.
The last thing to note is that the user’s device may not be as good as expected, at least not as good as our development machine. We can perform a CPU downgrade with the help of Chrome’s developer tools and then perform the relevant tests, the downgrade method is shown below.
If you need to access it on mobile, it’s best to limit the speed limit even lower, as performance tends to be worse on mobile.
4. Do not use @import
As a final mention, don’t use @import to introduce CSS, as I’m sure you rarely do.
The use of @import is not recommended for two main reasons.
First of all, using @import to introduce CSS affects parallel downloads by the browser. A CSS file referenced by @import is downloaded and parsed only after the css file that references it is downloaded and parsed, then the browser knows that there is another css file that needs to be downloaded, and then it downloads it, parses it, builds a render tree, and so on. This makes it impossible for the browser to download the required style files in parallel.
Second, multiple @imports can lead to a disrupted download order. In IE, @import can cause the download order of resource files to be disrupted, i.e., the js file that comes after the @import is downloaded before the @import, and disrupts or even destroys the parallel download of the @import itself.
So don’t use this method, just use the link tag.