Next.JS 中自定义字体

4,442 阅读12分钟

最近用Next.JS开发一官网类型的静态网页。里面涉及到了字体加载优化的方面,在这里进行记录下。这个网站用的是Next.JS13进行开发的。

CSS中使用自定义字体

说起在css中设置字体,大家都应该很熟悉 ,主要通过font-family这个属性进行设置。在页面上加载一些系统中不常用的字体,就要使用到 @font-face这个属性。这里先简单回忆下这两个属性的使用方法。

font-fmaily

语法:font-fmaily: | 。多种字体之间使用 “,”进行分隔。

  • font-fmaily-name从字面意思我们就可以知道,就是一个字体名称,比如:"Helvetica" 、"Times"等,需要注意的是:这些名称必须是系统中已经安装过的字体才能正常起作用 如果要使用动态从网络上加载的字体,那就要使用 @font-face 这个属性进行提前设置。
  • generic-name(通用名)指的是服务某些特征的一类字体名。以下内容引用MDN
    • serif 带衬线字体,
    • sans-serif 无衬线字体。即笔画结尾是平滑的字体。
    • monospace 等宽字体。即每个字体的宽度相同。
    • cursive 草书字体。这种字体有连笔的感觉,有的还有特殊的斜体效果。因为一般这种字体都有一点连笔效果,所以会给人一种手写的感觉。
    • fantasy 字体主要是那些具有特殊艺术效果的字体。
    • system-ui 从浏览器所处平台处获取的默认用户界面字体
    • math 针对显示数学相关字符的特殊样式问题而设计的字体:支持上标和下标、跨行括号、嵌套表达式和具有不同含义的
    • emoji 专门用于呈现 Emoji 表情符号的字体
    • fangsong 一种汉字字体,介于宋体和楷体之间。这种字体常用于某些政府文件。

有效的字体设置

// 用一句话来解决就是:
// 1. 要么字体名称都带引号,
// 2. 以标点符号或者数字开头的字体名必须使用引号包含

font-fmaily: "Gill Sans Extrabold", sans-serif;
font-fmaily: "Goudy Bookletter 1911", sans-serif;

/* 无效设置  */
font-fmaily: Goudy Bookletter 1911, sans-serif;

如果我们的语法不对,通过chrome的控制也可以看到。

@font-face

上面说了font-fmaily的用法,但是有一个前提,我对font-fmaily设置的字体名称在本地的计算机已经安装过才可以有作用,否则还是没有用的。如果想在用户的浏览器里显示本地没有安装字体样式,就需要使用@font-face这个属性。以下为MDN官方说明:

@font-face CSS at-rule 指定一个用于显示文本的自定义字体;字体能从远程服务器或者用户本地安装的字体加载。如果提供了 local() 函数,从用户本地查找指定的字体名称,并且找到了一个匹配项,本地字体就会被使用。否则,字体就会使用 url() 函数下载的资源。

这是一个叫做@font-face 的CSS @规则 ,它允许网页开发者为其网页指定在线字体。通过这种作者自备字体的方式,@font-face 可以消除对用户电脑字体的依赖。 @font-face 不仅可以放在在 CSS 的最顶层,也可以放在 @规则 的 条件规则组 中。

先查下Can I Use这个属性的兼容性:

没有顾虑,可以请放心使用。

语法

@font-face {
  font-family: MyHelvetica;
  src: local("Helvetica Neue Bold"),
  local("HelveticaNeue-Bold"),
  url(MgOpenModernaBold.ttf);
  font-weight: bold;
  font-style: normal;
  font-variant: small-caps;
  font-stretch: expanded;
  unicode-range: U+0025-00FF;
}

🤔 属性还是蛮多,让我们来一一进行分析。

属性说明例子
font-fmaily是当前加载字体的名称,即此处的name可以直接用于 font 或 font-family 属性。font-fmaily: "mydiyFont";后面给元素设置字体直接这样写.wrapper {font-fmaily: "mydiyFont";}
src要加载字体资源路径,可以是本地也可以是网络资源。注:这里使用的 Web fonts 仍然受到同域限制 (字体文件必须和调用它的网页同一域), 但可以使用 HTTP access controls (en-US) 解除这一限制。src: local("Helvetica Neue Bold"),url("FZCYS.woff2"),
font-weight与css中的font-weigh属性一样。后面会详细解释font-weight: bold
font-style与css中的font-style值一样。后面会详细解释font-style: norml
font-variant(看到这个属性的时候有点蒙逼,CSS还有这个属性,怎么没有用过😰)
font-stretch(😰,同上我怎么没有用过,是个假前端了)
unicode-range用于指定加载字体支持的unicode 字体范围,Google Fonts 可以通过指定该属性,用于分片加载字体。可以极大的减小文件体积详见unicode-range: U+26;
font-display用于设置@font-face在下载字体和加载完成之后,如何处理页面显示的。详见

上表,基本列出了对于@font-face所的属性的解释。但是有些属性自己确实没有用过,在这里再加强说明下:

  1. font-variant (仅对于英文字段有效果)

font-variant 属性是font-variant-caps, font-variant-numeric, font-variant-alternates, font-variant-ligatures, font-variant-east-asian(en-US)等属性的简写。你也可以使用简写 font 设定font-variant在 CSS Level 2 (Revision 1) 中的值(即normal 或 small-caps)

字体变形主要服务于英文字体(毕竟中文没有大小写这东西)。

可选值有: normal | small-caps | initial | inherit;

其中 small-caps值为小型大写字体。具体的效果可以看下面的代码效果:

  1. font-stretch

font-stretch 属性为字体定义一个正常或经过伸缩变形的字体外观,这个属性并不会通过伸展/缩小而改变字体的几何外形,如font-feature-settingsfont-variant属性,它仅仅意味着当有多种字体可供选择时,会为字体选择最适合的大小。

这又是一个仅对英文字体起作用的css属性,主要的作用就是用于设置英文字体的拉伸效果。语法如下:

font-stretch : normal | narrower | wider | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded

具体效果参见下面代码:

font-weight、font-style 结合说明

在初次见到@font-face的语法时,有点意见。之前上面这两个属性不是正常对于一个元素设置样式时的属性吗?在这里双出现了。按正常的语法理解font-weight和font-style分别对于元素的字宽字体样式设置。难道引入一个字体设置不同的字宽和样式也需要加载不同的字体文件?一般给设计师要字体文件,他们都会发送给我们的格式为ttf,而且文件体积极大。下面以 阿里巴巴普惠体 字体为例子:见以下代码片段:

从上面的代码看,在设置font-face我同时添加了font-weight属性(其实不设置字体也会加粗,主要源于字体文件支持),在设置元素字体时字体会自动加粗。同理对于font-style样式也有同样的作用

font-dispaly

常见的属性值如下:

  • auto:这使用浏览器的默认行为,这会有所不同。
  • block:文本首先隐藏一小段时间,但在可用时将更改为自定义字体。这一个值是说有一个无限的swap时期
  • swap:文本永远不会隐藏,并在可用时更改为自定义字体。这也提供了无限的交换期。
  • fallback:文本隐藏很短的时间(块期),然后有一个短的交换期。如果自定义字体未在交换期内加载,则根本不会加载。
  • optional:文本有一个非常短的块加载时间(~100 毫秒)。如果在该块期间未加载字体,则使用后备字体并且根本不加载自定义字体。但是,字体仍在后台下载和缓存。这意味着,在后续页面加载时,自定义字体将在缓存中可用,然后将立即加载。

Next.JS中使用自定义字体

上面说了这么多的css中使用字体的基本知识点,下面说下在Next.JS中使用自定义字体。在Next.JS 13之前的版本基本上都是和普通css中使用方法一样,直接CSS进行设置即可。在Next.JS13版本里,官方添加了一个新的组件:@next/font,用于专门处理在SSR中优雅的加载web字体(基本是对于css语法的封装)。这里简单说下用法:

整体的包分为 @next/font/google 和 @next/font/local,通过名字就能看的出来,一个是加载 Google Fonts 的字体,另一个是加载本地的字体文件。下面是引用官方的语法说明:

Keyfont/googlefont/localData typeRequired作用
srcString or Array of ObjectsRequired加载字体文件的URL地址
weightString or ArrayRequired/Optional同@font-face的font-weight设置
styleString or ArrayOptional同@font-face的font-style设置
subsetsArray of StringsOptional加载字体的子集,这个
axesArray of StringsOptional
displayStringOptional同@font-face的font-display设置
preloadBooleanOptional是否预加载
fallbackArray of StringsOptional如果网络加载字体文件失败时的,设置的字体样式
adjustFontFallbackBoolean or StringOptional
variableStringOptional对应的css变量名称
declarationsArray of ObjectsOptional

下面以一个例子来说明使用:@next/font/google进行加载google的字体,分别使用

  1. style 设置字体
  2. className设置字体
  3. css 变量方式设置字体

代码线上地址:codesandbox.io/p/sandbox/n…

一些问题

在Next.JS13的版本里,在@next/font的加持下,让我在开发ssr应用时,设置字体变行很容易且规范。但是也有一些问题:

  1. 使用 @next/font字体本来只用一种字体,到了nextwork里怎么多个字体文件?
import Head from "next/head";
import { Noto_Sans, IBM_Plex_Mono } from "@next/font/google";
import styles from "../styles/Home.module.css";
import cx from "classnames";

const notoSans = Noto_Sans({
  subsets: ["latin"],
  style: ["italic", "normal"],
  weight: ["300", "500", "700"],
  variable: "--noto-sans",
});


export default function Home() {
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={cx(styles.description, notoSans.variable)}>
          <div className={cx(notoSans.className, styles.fw300, styles.fsi)}>
            思源黑体 300 斜体
          </div>
          <div className={cx(styles.fw500)} style={notoSans.style}>
            思源黑体 500
          </div>
          <div className={cx(styles.font, styles.fw700)}>思源黑体 700</div>          
        </div>
      </main>
    </>
  );
}

一查看network好家伙直接加载了6个字体文件

注:这里主要是Google Fonts里对于字体文件的拆分很细,每一个weight和每一个style都是一个单独的字体文件。上面对于 notoSans文件的设置即为: 2 * 3 = 6

  1. 如果我有3个SSR页面,引用了5种字体。怎么做到加载页面时仅加载当前页面所需字体呢?

在最开始开发项目的时候,我仅仅在Next.JS项目下的 app.tsx文件中进行配置了字体,进行集中管理。但是导致的结果就是上面那个问题每次页面都会加载目前项目所有字体,无论是否有用。 故不要在在app.tsx文件中添加字体引入设置,这样就会导致所有的字体都会在每个页面进行加载。毕竟app.tsx这个文件是任意页面时候都会执行的。

要想做到每个页面只加载本身所需要的文件,只有在需要设置字体的组件中进行单独的设置,分散开才可以做到每个页面只加载自己需要的字体。缺点:就是不好管理。

  1. 页面上有一些字体,仅只为了展示几个字。怎么样做到加载的字体文件最小

Google Fonts目前是支持分片加载的,我们可以在Google的链接CSS里加上text参数。

这样子,获取的字体文件可以小到仅有几KB,我们再把这个文件下载下来,通过local的方式进行加载即可。基本这样搞减少加载几十KB是没有问题的。 唯一的缺陷是这个属性,@next/font并没有支持,暂时只能全手动下载到本地,然后使用local方式加载

  1. 使用 Google Fonts,在本地运行时会出现以下问题:

Failed to download `IBM Plex Sans` from Google Fonts. Using fallback font instead.(也就是运行时下载字体文件超时问题,暂时只能下载到本地进行加载。或者让本地环境设置科学上网)

一些琐碎的知识

在国内如何使用Google Fonts

由于众所周知的原因,Google的一些服务在国内是没有办法使用。如果开发时自己的电脑已经科学上网,其它同学的电脑没有科学上网,将会会现运行出错的情况(在dev里拉取字体失败的)。

  1. 使用中国个人开者的镜像站,但是功能没有原版的全:googlefonts.cn,(很不幸的是Next.JS 13的字体包没有关于拉取源的配置)
  2. 360 前端静态资源库

总结各大平台的默认字体加粗档位(字重)(出处)

注意,系统默认的 normal 字重是400;加粗的 bold 字重是700。

mac和win下渲染的支持不一样,会导致我写了一个font-weight:500,在win下没有效果

1、Mac & iOS 平台的“苹方”字体的字重:(有6种粗细,>=600的加粗效果是相同的)

  • 极细体:100
  • 纤细体:200
  • 细体:300
  • 常规体:400
  • 中黑体:500
  • 中粗体:600、700、800、900

2、Windows 平台的“微软雅黑”字体的字重:(只有两种粗细 ;>=600 才会加粗,而且加粗效果相同)

  • 不加粗的默认字体:100、200、300、400、500
  • 加粗字体:600、700、800

3、Android 平台的 Droid Sans 字体的字重:(只有 >=700才会加粗;而且加粗效果相同)

  • 不加粗的默认字体:100、200、300、400、500、600
  • 加粗字体:700、800

实战中,系统默认字体的加粗总结

  • 如果你做的软件产品只有苹果系统(比如iOS或Mac),可以使用各种粗细和字重。
  • 如果你做的软件产品包括了苹果系统(比如iOS或Mac)和非苹果系统(比如Android或Windows),建议直接使用normal(系统默认) 和 bold 这两种粗细。

参考资料