How to Speed Up Web Font Loading with Subsetting and FontFace APIs

This article explains why Chinese web fonts are large, how to analyze their structure, and presents practical techniques—including fontmin subsetting, SVG rendering, and the FontFace API—to dramatically reduce load time and control font display behavior for better user experience.

Aotu Lab
Aotu Lab
Aotu Lab
How to Speed Up Web Font Loading with Subsetting and FontFace APIs

Background

Large Chinese web fonts cause long load times and invisible preview before the font finishes loading.

Using Web Custom Fonts

Define a custom font with the CSS @font-face rule. A minimal cross‑browser example:

@font-face {</code>
<code>  font-family: "webfontFamily";</code>
<code>  src: url("webfont.woff2") format("woff2"),</code>
<code>       url("webfont.woff") format("woff"),</code>
<code>       url("webfont.ttf") format("truetype");</code>
<code>  font-style: normal;</code>
<code>  font-weight: normal;</code>
<code>}</code>
<code>.webfont { font-family: webfontFamily; }

Why Chinese Fonts Are Large

Chinese fonts contain thousands of glyphs, while Latin fonts have only a few dozen characters.

Each Chinese glyph has a far more complex outline, requiring many more points and therefore more data.

Using opentype.js to compare a Chinese font (8731 glyphs, 4.76 MB) with an English font (122 glyphs, 18 KB) illustrates the disparity.

Reducing Font File Size

unicode‑range

The unicode-range descriptor can limit which characters use a particular font, but it does not shrink the font file itself.

fontmin

fontmin

is a pure‑JavaScript tool that creates a subset of a font by removing unused glyphs. It delegates core processing to fonteditor-core, which works with the essential OpenType tables.

fontmin processing steps

Read the font file into an ArrayBuffer for binary parsing.

Parse the offset table and table directory to locate all tables in the font.

Read the glyf, maxp, and loca tables to obtain glyph outlines, glyph count, and glyph offsets.

Collect bounding‑box values ( xMin, xMax, yMin, yMax) and horizontal metrics ( hmtx) for each retained glyph.

Re‑calculate offsets, rewrite the offset table and table records in alphabetical order, and write the updated tables to a new font file.

Because fontmin only processes a fixed set of tables, optional tables such as vhea and vmtx are discarded, which can affect vertical text rendering.

Example usage

const Fontmin = require('fontmin');</code>
<code>const Promise = require('bluebird');</code>
<code>async function extractFontData(fontPath) {</code>
<code>  const fontmin = new Fontmin()</code>
<code>    .src('./font/senty.ttf')</code>
<code>    .use(Fontmin.glyph({ text: '字体预览' }))</code>
<code>    .use(Fontmin.ttf2woff2())</code>
<code>    .dest('./dist');</code>
<code>  await Promise.promisify(fontmin.run, { context: fontmin })();</code>
<code>}</code>
<code>extractFontData();

Dynamic workflow: capture user input → subset the font with fontmin → upload the subset to a CDN → generate an @font-face rule (or embed as a base64 data URL) → insert the rule into the page. This approach requires two HTTP requests (CSS and font file).

SVG Rendering Alternative

Instead of sending a subset font, extract glyph outlines from the glyf table using opentype.js and render them directly as SVG path elements, eliminating the extra request.

Controlling Font Display Before Loading

Browsers have a “block period” (FOIT – Flash of Invisible Text) and a “swap period” (FOUT – Flash of Unstyled Text). The font-display descriptor controls this behavior. The block value hides text until the font loads, but browsers fall back after roughly 3 seconds, causing a flash.

FontFace API

The CSS Font Loading API allows programmatic control of font loading and visibility:

/**</code>
<code> * Load a font file and add it to the document.</code>
<code> * @param {string} path Font file URL (woff2 preferred).</code>
<code> */</code>
<code>async function loadFont(path) {</code>
<code>  const fontFace = await new FontFace('fontFamily', `url('${path}') format('woff2')`).load();</code>
<code>  document.fonts.add(fontFace);</code>
<code>}

Set the preview element’s opacity: 0, await loadFont, then set opacity: 1 so the text remains hidden until the custom font is fully loaded, preventing FOIT/FOUT flashes.

Conclusion

The challenges of large Chinese web fonts can be addressed by subsetting with fontmin, rendering glyphs as SVG, and controlling load timing with the FontFace API. While fontmin handles the core tables, it discards vertical‑metrics tables, so developers should test vertical text rendering when using it.

JavaScriptWeb fontsFont Subsetting@font-facefontmin
Aotu Lab
Written by

Aotu Lab

Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.