How To Best Load Fonts for Web Applications
I have tried a couple different methods for loading fonts for web applications. I want to find the best method of accomplishing loading fonts.
About Fonts
- I use Anuphan, sans-serif for this site because I think it is a good, readable font.
- The font was developed by Mint Tantisuwanna - a type designer at Cadson Demak
- Anuphan is a Cadson Demak version of IBM Plex Thai
- Cadson Demak is a high-profile foundry and typographic consultancy in Thailand
- The IDM Plex typeface is a corporate typeface family.
- Over 60% of websites use custom fonts, so the use of custom fonts is definitely the standard on the web.
- Custom fonts give you the ability to include different styles of the font with a
@font-face
declaration in CSS or by loading the font using a<link>
tag - Anuphan has 7 different weights that you can load.
- I am using writing this note to see what the best way of loading a font is. Currently, I host the fonts on the server and request them in CSS files.
- I use
font-display: swap;
to swap the default font with the loaded font when the the requested font is loaded. - I used to serve font-weights of 100, 200, 300, 400, 500, 600, and 700 to give the user the ability to have a highly customizable experience, but I don't think this is the best way to go about loading fonts.
- I am trying to figure out what is the best combination of:
- I use
- Requesting font method
@font-face
CSS attribute values- Amount of custom font styles to load
- that result in the best experience.
Resources:
- Web Dev Resource on Best Practices for Loading Fonts
- Optimize Web Font Loading
- CSS Size-Adjust Property
- More than you ever wanted to know about font loading on the web
Notes on Best Practices:
- Introduction
- There are two main ways in which web fonts impact performance:
- Delayed Text Rendering
- The webpage will not render text until the web font has loaded. This slows down a webpage's First Content Paint
- Layout Shifts
- Font Swapping has the potential to cause content layout shifts
- This is the problem that I am currently experiencing, but I did have the problem of fonts affecting First Content Paint before switching the
font-swap
property.
- Font Loading
- You can check when the font is requested and downloaded in the Network tab of dev tools
- The font declared using
@font-face
is only downloaded when an element that uses the font is downloaded
This might affect how I bundle KaTeX, or how I use KaTeX - I currently use KaTeX in some dialogs and other places in content where the content is not user-generated, which could slow down loading. I might want to remove the KaTeX from everywhere that is not ugc to improve KaTeX loading.
Most sites would benefit form inlining font declarations and other critical styling in the <head> of the main document rather than including them in an external stylesheet. This allows the browser to discover the font declartion sooner as the browser doesn't need to wait for the external stylesheet to load.
- If you use fonts loaded from other sites, you should consider using the
link
tag with therel="preconnect"
attribute to preload the fonts. This will take browser resources away from other resources that you need to load.
- If you use fonts loaded from other sites, you should consider using the
- Font Delivery
- Self Hosting Fonts
- If you are using a self-hosted font, confirm that your site is using a content delivery network and HTTP/2.
- You should also apply some font file optimizations that third party font providers typically provide - such as font subset-ting and WOFF2 compression.
- Experts now recommend only using WOFF2 due to its widest browser support and best compression.
- Subset Fonts
- Font files typically include a large number of glyphs for all the various characters they support.
- You may not need all those characters.
- You can use the
unicode-range
descriptor in the@font-face
declaration informs the browser which characters a font can be used for.
- I think this is the range for English:
- This is the range for basic Latin characters:
U+0020-007F
- The same font file is still downloaded when one of the characters in the unicode range is included.
- A subset font includes a smaller portion of the glyphs that were contained in the original font file.
- See the section below on removing unwanted glyohs from fonts.
- Use Fewer Web Fonts
- A system font is the default font used by the user interface of the user's device. System fonts typically vary by operating system and version. Because the font is already installed, the font does not need to be downloaded.
- To use the system font in CSS, list the
system-ui
as thefont-family
- Font Rendering
- The
font-display
property informs the browser how it should proceed with text rendering when the associated web font has not loaded. It is defined per font-face. - Possible values for
font-display
:
- Possible values for
- The
Value | Block Period | Swap Period |
---|---|---|
Auto | Varies | Varies by Browser |
Block | 2-3 Seconds | Infinite |
Swap | 0ms | Infinite |
Fallback | 100ms | 3 seconds |
Original | 100ms | None |
- Block Period
- The block period begins when the browser requests a web font. During the block period, if the web font is not available, the font is rendered in an invisible fallback font and thus the text will not be visible to the user. If the font is not available at the end of the block period, it will be rendered in the fallback font.
- Swap Period
- The swap period comes after the block period. If the web font becomes available during the swap period, it will be "swapped" in.
- For this websites using a custom font where display text quickly is a top priority and where you still want to ensure the web font is used, use
font-display: swap
, but make sure to deliver the font early enough that it does not cause a layout shift. The downside of this option is the jarring shift when the font arrives late. - To reduce the CLS impact, you can use the new
size-adjust
attributes.
- For this websites using a custom font where display text quickly is a top priority and where you still want to ensure the web font is used, use
Remove Unwanted Glyphs from Fonts
Problem:
- Font files often include many glyphs that you will not need to use in your application
- Failing to remove these glyphs can result in larger font files which will slow down file download speed, which in turn will make the Content Layout Shift (if you are using
font-display: swap;
very noticeable. - To prevent a noticeable / large content layout shift caused by long download times for font files, you can try to speed up the file download speed by removing glyphs that are not needed from the font files.
How to Remove Unwanted Glyphs:
- Download fonttools:
pip install fonttools
- Run the subset command to only include the characters that you need
- What are the characters that we need?
GPT 4o Response:
Question:
give me the unicode-list css property for a @font-face declaration for a font that should only contain the characters that are commonly used on english websites
To create a custom font with a @font-face declaration that only includes characters commonly used on English websites, you can use the unicode-range CSS property. The following unicode-range values cover the basic Latin characters, punctuation, and a few additional characters that are frequently used in English.
Here's a @font-face example for such a font:@font-face {
font-family: 'MyCustomFont';
src: url('MyCustomFont.woff2') format('woff2'),
url('MyCustomFont.woff') format('woff');
unicode-range: U+0020-007F, /* Basic Latin (includes ASCII) */
U+00A0-00FF, /* Latin-1 Supplement */
U+2010-201F, /* Punctuation */
U+2020-2027, /* Additional Punctuation */
U+2030-203E, /* Additional Punctuation */
U+2044, /* Fraction Slash */
U+20AC, /* Euro Sign */
U+2122, /* Trade Mark Sign */
U+2190-2199, /* Arrows */
U+2200-22FF, /* Mathematical Operators */
U+25A0-25FF, /* Geometric Shapes */
U+2600-26FF; /* Miscellaneous Symbols */
}
- Basic Latin (U+0020-007F): This range covers standard English letters, digits, and basic punctuation.
- Latin-1 Supplement (U+00A0-00FF): This range includes additional punctuation and letters with diacritical marks.
- Specific punctuation ranges and symbols: These ranges include various punctuation marks, mathematical operators, geometric shapes, arrows, and common symbols that might be used in English text.
- The command that should be run is:
pyftsubset PATH_TO_FONT_FILE [glyph...] [-option=value]...
- At least one glyph or one of -gids, -gids-file, -glyohs, -glyphs-file, -text, -text-file, -unicodes, or -unicodes-file, must be specified
- Using the chat-gpt response above, the command would be:
pyftsubset PATH_TO_FONT_FILE --unicodes="U+0020-007F,U+00A0-00FF,U+2010-201F,U+2020-2027,U+2030-203E,U+2044,U+20AC,U+2122,U+2190-2199,U+2200-22FF,U+25A0-25FF,U+2600-26FF" --flavor="woff2" --ignore-missing-glyphs --verbose
- I put these commands in a python file to make it easier to convert multiple files:
import os
# Filenames to subset font for
filenames = ["Anuphan-Bold.ttf","Anuphan-ExtraLight.ttf","Anuphan-Light.ttf","Anuphan-Medium.ttf","Anuphan-Regular.ttf","Anuphan-SemiBold.ttf"]
# Unicode glyphs to keep in output font
unicodes = "U+0020-007F,U+00A0-00FF,U+2010-201F,U+2020-2027,U+2030-203E,U+2044,U+20AC,U+2122,U+2190-2199,U+2200-22FF,U+25A0-25FF,U+2600-26FF"
for filename in filenames:
path_to_input_file = os.path.join(os.path.curdir,"input-fonts",filename)
path_to_output_file = os.path.join(os.path.curdir,"output-fonts",filename.replace(".ttf",".woff2"))
cmd = "pyftsubset \"" + path_to_input_file + "\" --unicodes=\"U+0020-007F,U+00A0-00FF,U+2010-201F,U+2020-2027,U+2030-203E,U+2044,U+20AC,U+2122,U+2190-2199,U+2200-22FF,U+25A0-25FF,U+2600-26FF\" --output-file=\"" + path_to_output_file +"\" --flavor=\"woff2\" --ignore-missing-glyphs --verbose"
print(cmd)
os.system(cmd)
- The results of subset-ting the Anuphan font in terms of file size and download times from the web server can be seen below
- The red border denotes that the specified font is not being used in production.
- I chose to only include some font weights to decrease size of application.
- The method of measuring download speeds is not scientific, but I just wanted to get an idea if there was a great affect
Filename: | Size Before Subsetting: | Size After Subsetting | Download Before Subsetting | Download After Subsetting |
---|---|---|---|---|
Anuphan-Bold.woff2 | 36kB | 14kB | They all ended up being about the same. | |
Anuphan-ExtraLight.woff2 | 38kB | 15kB | ||
Anuphan-Light.woff2 | 38kB | 15kB | ||
Anuphan-Medium.woff2 | 42kB | 15kB | ||
Anuphan-Regular.woff2 | 38kB | 14kB | ||
Anuphan-Thin.woff2 | 34kB | N/A | ||
Anuphan-SemiBold.woff2 | 42kB | 15kB |
Optimizing Web Font Loading
Lazy loading of fonts carries an important hidden implication that may delay text rendering. The rbowser must construct the render tree, which is dependent on the DOM and CSSOM trees. As a result, font requests are delayed well after other critical resources, and the browser may be blocked from rendering text until the resource is fetched.
By preloading WebFonts and using font-display
to control how browsers behave with unavailable fonts, you can prevent blank pages and layout shifts due to font loading.
Preloading WebFont Resources
- If you know you will need the resource, using
<link rel="preload">
will trigger a request for the web font early in the critcial rendering path, with or without having to wait for the CSSOM to be created.
Customize Text Rendering Delay
- You can customize how browsers behave when rendering text that uses a
font-family
that is not yet available using thefont-display
property inside a@font-face
declaration. This was mentioned above.
The Font Loading API
- If you need additional customizations for loading fonts, you can use the Font Loading API
Proper Caching is a Must
- Font resources are typically static resources that don't see frequent updates
- As a result, they are ideally suited for a long max-age expiry - ensure that you specify a conditional ETag header and an optimal Cache-Control policy for all font resources
- Serving fonts with a cache-first strategy is appropriate for most use cases
CSS size-adjust for @font-face
As a web font loads, you can now adjust the document font sizes and prevent layout shift when switching between fonts.
- See this example of size adjust in action to see how using size-adjust allows for switching between font families in a seamless manner.
- Or, use this codepen to see why the size-adjust property is needed when switching between fonts
- Changing the typeface results in height differences in the results
- Visually jarring content shifts
- Moving interactive target areas
- This can help with problems with cumulative layout shift
- The example below uses
size-adjust
to scale up each glyph to 150% their original size.
@font-face {
font-family: "Adjusted Typeface";
size-adjust: 150%;
src: url(some/path/to/typeface.woff2) format('woff2');
}
- To create a seamless swap of fonts, hand ajust the size-adjust attribute or try this size-adjust calculator by Malte Ubl
Calibrating fonts
- Strategies for
size-adjust
calibration: - Remote target
- Adjust the local fonts toward downloaded fonts
- Use the
@font-face
declaration to adjust a local font in anticipation of font swapping for a custom font
@font-face {
font-family: "Adjusted Regular Arial For Brand";
src: local(Arial);
src: local(sans-serif);
size-adjust: 90%;
}
@font-face {
font-family: "Rad Brand";
src: url(some/path/to/a.woff2) format('woff2');}
html {
font-family: "Rad Brand", "Adjusted Regular Arial For Brand", sans-serif;
}
- The example above adjusts the local Arial font in anticipation of a loaded custom font, for a seamless swap.
- This strategy has an advantage of offering designers and developers the same font for sizing and typography.
- Local target
- Adjust downloaded fonts towards local target fonts
- This strategy has the advantage of rendering without any adjustments, then adjusting any incoming fonts to match.
- You can have finer tuning - other than just generic scaling - when loading fonts by using the
ascent-override
,descent-override
, andline-gap-override
properties. - These aren't available on safari though
- The Compatibility table for size-adjust shows that it is available in all major browsers, so it is probably better to just use size-adjust for now rather than customizing the three properties listed above.
Conclusion
- For fonts that are used as the main font of an application, or fonts that are used a lot through the application, it is best to load the fonts in a way that they are not blocked by CSSOM. This can be accomplished by loading the fonts inline before any CSS is declared or by preloading the fonts using
<link rel="preload">
- For fonts that are used sparingly, you should pay to attention to where you use the fonts in the application. If the font is declared in CSS, then whenever the font is declared, the font will be loaded.
- You should optimize font files by removing glyphs that you don't need from the fonts and by compressing the fonts into the
.woff
file format. - Use the size-adjust property to prevent Cumulative layout shift.
- For more advanced font-loading techniques, look into the font loading API in JavaScript/CSS
What I Plan on Creating
- I plan on creating a project page where you can compare fonts in order to see what size-adjust property you need to use. (in case I don't come back to this note the link should look like: https://frankmbrown.net/projects/configure-size-adjust-property
- The size-adjust calculator by Malte Ubl does not allow you to upload your own fonts, and it does include Anuphan. I need to find out what size-adjust value I need to use to minimize the Cumulative Layout Shift for Anuphan.
- The page should allow users to upload font files, check the font files on the server, upload the font files to a s3 bucket (should have a cloudfront edge), load the font on the client, and allow the user to check different combinations of the size adjust property to see which one works.
Unanswered Questions
- Would keeping the font file on a server in an edge location through a service like Amazon s3 be quicker than keeping the font file on the backend server?
Comments
There are currently no comments to show for this article.