Problem statement
When does a browser request to download a font?
By default, all fonts are lazily loaded on the browsers and the request to download fonts is only initiated during the construction of CSSOM (CSS Object Model) which happens only after the creation of DOM (Document Object Model).
While parsing a CSS file, if a browser finds a custom font is used, it will make an HTTP request to download the font either on the same server or on a cross-origin server depending on what is provided on the src attribute of @font-face.
Let's take an example where we have an index.html and main.css
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Preloading Fonts</title>
<link rel="stylesheet" href="main.css" as="style" />
</head>
<body>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
</p>
</body>
</html>
main.css
@font-face {
font-family: Tangerine;
src: url("Tangerine-Regular.ttf");
}p {font-family: "Tangerine";}
In the above diagram which depicts the critical rendering path (waterfall steps ), the request to download the font happens in T2 phase. Until the font is successfully downloaded or the download process passes the following default Timeout period depending on the type of browser, the user will see a blank screen.
For example, in the case of Chrome, we can observe the following actions:
- A request to download the font is made.
- If the font is downloaded within 3 seconds, the user will first see a blank screen and will be immediately swapped with the custom font after it is downloaded. This phenomenon is called a Flash of Invisible Text (FOIT).
- If the font does not download within 3 seconds, the user will first see a blank screen for 3 seconds followed by the system font (fallback) until the custom font is downloaded and swapped or stay with the system font in case of download failure. This phenomenon is called a Flash of Unstyled Text (FOUT).
Solution
1. Preload
We can use a preloading strategy to minimize the risk of FOIT. The main idea of preloading is to start the process of downloading the font very early in the process of the critical rendering path, T1 phase, in the above diagram. The font will likely be downloaded completely when the process reaches to T2 phase, and the browser can paint the text with custom font right away if there are no other blocking resources. In case, if the font is served from a cross-origin server, href property below should be adjusted accordingly.
Note: crossorigin must be added on the link tag below even if the font is served from the same origin.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Preloading Fonts</title>
<link rel="preload" href="Tangerine-Regular.ttf" as="font" crossorigin />
<link rel="stylesheet" href="main.css" as="style" />
</head>
<body>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
</p>
</body>
</html>
However, if the latency to download the font is very much high even after we start the download process early in the stage, then preloading will also not help and we need to look for another alternative which we will discuss shortly.
2. DNS prefetch
In cases where the cross-origin server is used and many fonts need to be downloaded, we can use the DNS prefetch strategy instead of preloading each font explicitly. The main goal of this approach is to start the handshaking process including the DNS resolving process and establishing the connection with the server early in the process so that later in the process of critical rendering path, downloading resources becomes faster since the connection to the server which might include several network roundtrips has already been set up. This also helps to optimize the rendering of other resources such as images, javascript etc if they are served from the same cross-origin server.Note: If all the resources are on the same origin server, then this is not useful.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Preloading Fonts</title>
<link rel="dns-prefetch" href="https://example.com" />
<link rel="stylesheet" href="main.css" as="style" />
</head>
<body>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
</p>
</body>
</html>
3. font-display CSS property
We can use the font-display CSS property. If we want to get rid of the FOIT completely, we can use swap value. With this, it is guaranteed that we will see the system font right away without the flash of invisible text and when the custom font gets downloaded, it will swap the system font (FOUT).
@font-face {
font-family: Tangerine;
src: url("Tangerine-Regular.ttf");
font-display: swap; /*auto | block | optional | fallback | optional*/
}
p {
font-family: "Tangerine";
}
- It will most likely trigger layout shifts (movement of page content without user interaction) because the positions of custom and system fonts might be different for the same text on the viewport. Also, the browser has to recalculate the layout and paint it again on the screen with the new coordinates and dimensions.
- It will degrade the user experience because of layout shifts and the interchanging of different fonts.
There are 5 different possible values of font-display property, and different browsers have their specific block and swap period for each of these values (See the table below).
- block: Hides text up to a defined block period while waiting for the custom font to download, and always swaps custom font when it loads successfully.
- swap: Shows text as soon as possible with the system font, and always swaps the custom font when it loads successfully.
- fallback: Hides text up to a defined block period, then swaps the custom font only if it loads within the defined swap period. If the font can't be swapped with the defined swap period, the system font will be applied even after the successful download of the custom font in the future.
- optional: Hides text up to a defined block period, and only uses the custom font if it is available within this block period. If the custom font is not downloaded or gets downloaded after the block period, it will not be applied since there is no swapping at all. This also means there are no risks of layout shifts.
- auto: Based upon the default browser implementation and varies from browser to browser.
My opinions on which font-display value to pick
- block: I think displaying a blank screen for 3 seconds, for example in the case of chrome, is not ideal. Users might leave the page getting frustrated before the text gets painted on the screen.
- swap: Swapping a font is bad for a user experience and prone to layout shifts. However, if you must have a custom font, for example, you have a company logo that only makes sense when it is rendered with the proper font or some other branding information, then this might be a good option. But there is a trade-off.
- fallback/optional: I think both fallback and optional are good picks because they care more about minimizing layout shifts and blank screens with a trade-off of not displaying the custom font in case it is not downloaded on time.
- auto: I think it is hard to get the same user experience across different browsers since they have different implementations.
Summary
Following are some of the ways to optimize the loading and rendering of web fonts which ultimately improves the loading speed of the web application.
- Preload fonts
- DNS prefetch
- Font-display property of CSS
I also have a video of this blog post on youtube.
https://www.youtube.com/watch?v=wnpMeYARV4g&ab_channel=FrontendMentor