如何仅在本地无对应字体时预加载Roboto字体?
解决方案:动态检测系统字体 + 按需预加载自定义Roboto
要实现仅在本地无San Francisco或Roboto时预加载自定义字体,同时避免不必要的资源浪费,我们可以结合JavaScript字体检测和动态资源注入的方式,具体步骤如下:
1. 字体检测核心函数
首先需要一个函数来判断目标字体是否在本地可用。原理是通过比较同一文本在目标字体和基准字体(如sans-serif)下的渲染尺寸差异——如果尺寸不同,说明目标字体存在:
function isFontAvailable(fontFamily) { // 创建隐藏的测试元素,避免影响页面布局 const testEl = document.createElement("span"); testEl.style.cssText = ` position: absolute; visibility: hidden; font-size: 100px; white-space: nowrap; font-family: sans-serif; `; document.body.appendChild(testEl); // 记录基准字体的尺寸 const baseWidth = testEl.offsetWidth; // 切换到目标字体 testEl.style.fontFamily = `"${fontFamily}", sans-serif`; const targetWidth = testEl.offsetWidth; // 清理元素 document.body.removeChild(testEl); // 尺寸不同则字体存在 return targetWidth !== baseWidth; }
2. 按需预加载与字体配置
在页面加载早期(建议放在<head>中执行),检测系统字体情况,仅当San Francisco和系统Roboto都不存在时,动态添加预加载标签并配置自定义字体:
// 尽早执行,避免资源加载延迟 (function() { // 检测是否有苹果原生San Francisco字体 const hasSF = isFontAvailable("-apple-system"); // 检测是否有安卓系统自带Roboto字体 const hasSystemRoboto = isFontAvailable("Roboto"); if (!hasSF && !hasSystemRoboto) { // 动态创建预加载标签 const preloadLink = document.createElement("link"); preloadLink.rel = "preload"; preloadLink.as = "font"; preloadLink.href = "/assets/fonts/roboto.woff2"; preloadLink.type = "font/woff2"; preloadLink.crossOrigin = ""; // 同域资源也需添加,避免浏览器跨域限制 document.head.appendChild(preloadLink); // 预加载完成后,注册自定义Roboto字体 preloadLink.onload = function() { const fontStyle = document.createElement("style"); fontStyle.textContent = ` @font-face { font-family: 'CustomRoboto'; src: url('/assets/fonts/roboto.woff2') format('woff2'); font-display: swap; /* 优先显示系统字体,避免文本闪烁 */ font-weight: normal; font-style: normal; } body { font: normal 1em -apple-system, Roboto, 'CustomRoboto', sans-serif; } `; document.head.appendChild(fontStyle); }; } else { // 直接使用系统字体,无需加载自定义资源 const systemFontStyle = document.createElement("style"); systemFontStyle.textContent = ` body { font: normal 1em -apple-system, Roboto, sans-serif; } `; document.head.appendChild(systemFontStyle); } })();
3. 关键优化点
font-display: swap:确保字体加载完成前,页面先显示 fallback 字体,避免FOIT(不可见文本闪烁),提升用户体验。- 尽早执行脚本:将检测脚本放在
<head>最顶部,确保在页面渲染前完成判断,避免资源加载延迟。 - 避免全局预加载:仅在真正需要时才触发字体预加载,减少不必要的网络请求,降低页面体积。
- 跨域处理:即使字体是同域资源,也需添加
crossOrigin=""属性,否则部分浏览器会拒绝使用预加载的字体资源。
4. 备选方案:结合CSS @font-face 优先级
如果不想用JS检测,也可以通过CSS的字体优先级配置,让浏览器仅在系统字体不存在时加载自定义字体,但无法实现预加载(浏览器会在需要时才下载):
@font-face { font-family: 'Roboto'; src: url('/assets/fonts/roboto.woff2') format('woff2'); font-display: swap; font-weight: normal; font-style: normal; /* 降低自定义字体的优先级,让系统字体优先匹配 */ font-family: 'Roboto', fallback; } body { font: normal 1em -apple-system, Roboto, sans-serif; }
不过这种方式无法实现预加载,字体下载时机较晚,适合对加载速度要求不极致的场景。
内容的提问来源于stack exchange,提问作者Derek




