头图

前端工程师需要知道的字体知识

随着网页个性化设计和品牌特色的需要,在网页中使用特定的字体将成为越来越常见的需求。现代字体设计经历了数十年的发展,已经积累了成熟的标准和规范,但对于许多前端开发者来说可能可能还比较陌生,本文就前端开发过程中可能遇到的字体知识做一个梳理和介绍。

博客原文:https://www.hozen.site/archives/64/

字体基础

字体标准

字体的设计和使用和其他任何工业设计产品一样,需要统一的标准和规范。

最初的计算机字体是点阵字体,放大后会有明显的马赛克。为了高清打印和缩放的需求,Adobe 公司于 1984 年发布了 PostScript 语言,使用三次贝塞尔曲线来绘制字体,极大的提高了字体打印和屏幕显示质量[1]。很快苹果又推出了 TrueType 字体标准,采用二次贝塞尔曲线绘制字体,被广泛应用于 macOS 和 Windows 系统中[2]

1996 年微软联合 Adobe 在 TrueType 的基础上推出了 OpenType 字体标准,该标准整合了 PostScript 字体和 TrueType 字体的特点,并新增了许多新的特性。OpenType 陆续得到苹果和谷歌等公司的支持,在主流计算机平台应用广泛,目前已经成为字体设计的主要发展趋势[3]

总的来说,字体(轮廓字体 Outline fonts)标准整体上有 3 种,但对于普通用户和开发者来说 TrueType 和 Opentype 比较常见。

字体文件

字体设计完成后将打包成特定文件分发给用户,字体文件根据标准的不同采用不同的后缀名。TrueType 字体以 .ttf 作为后缀名。OpenType 字体根据不同的情况采用 .otf, .ttf, .otc.ttc 为后缀,其中 OTF 一般表示基于 PostScript 技术,对应地,TTF 表示使用 TrueType 技术。而 OTC 和 TTC 则表示字体集合(Collection),即一个文件中包含多种字体(例如多种语言)[4]。这 4 种字体文件在主流操作系统和网页设计中都是可用的。

除此之外,还有专用于网页的网页开放字体(Web Open Font Format)格式,其设计标准通常为 OpenType 或 TureType,但在文件打包和压缩中专门为网络传输做了优化。WOFF 字体在主流浏览器中都得到很好的支持,文件后缀名为 .woff 以及 .woff2

字体类型

常见的字体可以分为“衬线(serif)”、“无衬线(sans serif)”、“等宽(monospace)”等类型。衬线是指在字体笔画末端有小装饰。在论文写作中常用的 Times New Roman 字体和中文宋体便是衬线字体。无衬线字体的字体线条则相对简约,例如 Arial 和“微软雅黑”以及“苹方”。等宽字体则表示每个字符占据相等的宽度,这一点衬线和无衬线字体是无法保证的,例如小写字母 i 在非等宽字体中往往占据较小的字宽。

由于衬线字体具有更丰富的细节且更加精致,所以多用于印刷和高清显示。无衬线字体在屏幕显示上更加普遍,主流浏览器和操作系统的默认字体一般都是无衬线字体。但随着屏幕分辨率的提高和显示技术的提升,在屏幕显示中使用衬线字体可以使文本页面更加精致具备设计感。例如我的主页首页部分字体就使用了思源宋体。等宽字体由于具备字符等宽的特点往往被用于代码的编辑和显示。

字体家族 Font Family

前文提到的诸如“Times New Roman“,“微软雅黑”等字体其实都是字体家族的概念。字体家族代表了统一的设计风格,但字体往往需要不同的粗细(字重)和样式(斜体、字宽等)。因此一个字体家族可以包含不同的子字体来实现不同的字体样式。具体地,字体样式包括字重、字宽、倾斜和视觉尺寸等方面,通过这些样式的组合便产生不同的子字体。但除非特别需要,对于屏幕显示来说常用的子字体样式一般只包括字重和倾斜。例如,苹方字体家族字体包括常规体(Regular)、极细体(Ultralight)、纤细体(Thin)、细体(Light)、中黑体(Medium)和中粗体(Semibold)6 个子字体。当然也有一些发布较早的字体只包含一种样式,即一个字体文件。

当我们使用文字处理软件或者 CSS 属性对字体应用“加粗”和“倾斜”等样式时,系统会查找对应样式的子字体是否可用,如果可用则使用对应的子字体,否则则通过计算对字体进行强制的加粗和倾斜。虽然这两种方式最终都将字体进行了加粗或倾斜,但效果是完全不一样的。子字体不同的样式通过了严格的设计,使其具备统一的美感。而后者只是简单粗暴的加粗或倾斜,视觉效果往往比不上前者。例如,下图中分别使用思源宋体的常规体进行计算加粗(上)和直接使用思源宋体的 Bold 字体(下),可以明显看出上面的文字在细节和观感上相去甚远。

计算加粗和子字体的效果对比

可变字体(Variable Font)

一款优秀的字体会提供多种字重的子字体,这样能保证在使用不同字重时能够保持优秀的观感。但每一款单独的字重或样式的字体往往需要单独的字体文件,这导致了当字体样式变多时字体文件数量增加,尤其对于网络页面来说会增加请求次数和流量负担。

为了解决这一问题,在 OpenType 规范的中,Adobe、微软、苹果和谷歌于 2016 年共同推出了可变字体Variable font)的标准,这一标准改变了字体样式的设计和使[5]]。就字重来说,不再需要多个字重的字体文件,一个字体文件即可使用多种字重。并且,字体字重不再被离散的划分为“常规”,“中黑”等有限的个数,而是能够通过调整字重参数获得任意粗细,实现字重的无级调节。像下图中 MIUI 的动态交互效果在没有可变字体时是很难实现的。

基于小米兰亭可变字体实现的字重无极动态变化

OpenType 要求可变字体文件需要在命名使用 VF标注,例如 Selawik-VF.ttf。因此我们从字体文件名往往可以分辨该字体是否是可变字体。

CSS 属性

默认字体名称

CSS 属性 font-family 用于指定字体,并且规定了 5 种默认的字体名称:serif, sans-serif, monospace, cursive, fantasy,除了前面提到的 3 种字体,cursivefantasy 分别表示手写字体和装饰字体。当使用这些默认字体名称时,具体使用何种字体是由浏览器决定的,往往也会根据操作系统的不用有所不同。

虽然使用默认的字体属性可能会造成跨平台字体表现不一致的情况,但对于大多数功能性网页来说不必过分担心,因为在主流平台中字体经过了严格设计,规范性和易用性是完全经得住考验的。

字体栈

使用 font-family 除了上述 5 种默认字体名称外,可以指定具体的字体名称,例如 Times New Roman。当指定的字体不可用(系统未安装该字体或远程字体加载失败)时则会使用浏览器默认字体。

为了防止字体退化为默认字体,可以为 font-family 指定多个字体,例如 font-family: "Times New Roman", Times, serif。浏览器会按照属性列出的先后顺序查找和使用字体,直到找到第一个可用的字体,当字体都不可用时则使用浏览器默认的字体。

在字体栈的最后使用一个默认字体名称是不错的选择,因为即使浏览器没有找到合适的字体也会使用一个还算合适的字体。否则,当所有字体都不可用时,为了强调这一点,相应文字将被赋予浏览器的默认衬线字体 - 通常是 Time New Roman - 这对于 sans-serif 字体是不利的!

网络安全字体

前文提到,在不同的浏览器和操作系统中网页的默认字体会有所不同。当对页面有跨平台时保持字体统一的要求时,可以使用“网络安全字体“,安全字体即挑选出在主流平台都默认安装的字体,来保证跨平台时字体的可用性。

以下是拉丁文字体的网络安全字体:

字体名称泛型注意
Arialsans-serif使用 Helvetica 作为 Arial 首选替代
Georgiaserif
Times New Romanserif使用 Times 作为 Times New Roman 的首选替代方案
Courier Newmonospace使用 Courier 作为 Courier New 的首选替代方案
Trebuchet MSsans-serif应该小心使用这种字体——它在移动操作系统上并不广泛
Verdanasans-serif

其中“首选替代方案”往往是因为同一款字体的不同版本在新旧操作系统上安装情况或名称不一致,使用字体栈包含各种可能的名称来保证可用性。

CSS 自定义字体

CSS3 引入 @font-face 规则,可以从远程服务器加载字体文件或使用户本安装的字体。这一特性使得网页设计不必局限于默认字体或“网络安全字体”,使得网页设计更具个性和品牌表达力。

一个使用 @font-face 的加载远程字体的典型例子:

<html>
<head>
  <title>Web Font Sample</title>
  <style type="text/css" media="screen, print">
    @font-face {
      font-family: "Bitstream Vera Serif Bold";
      src: url("https://mdn.github.io/css-examples/web-fonts/VeraSeBd.ttf");
    }

    body { font-family: "Bitstream Vera Serif Bold", serif }
  </style>
</head>
<body>
  This is Bitstream Vera Serif Bold.
</body>
</html>

但是,请注意!上面例子中的文字将以“Times New Roman”字体展示,这是因为 @font-face 的远程字体文件加载同样需要遵循同源策略,我们在加载 github.io 的跨域资源将加载失败。还记得前文提到,当字体栈不可用时会使用浏览器的默认衬线字体作为提醒吗?这就是为什么上例的文字最终会以“Times New Roman”字体展示。

@font-face 常用的属性有:

  • font-family,指定字体的名字,将会被用于 font 或 font-family 属性
  • src,远程字体文件位置的 URL 或者用户计算机上的字体名称,可以使用 local 语法通过名称指定用户的本地计算机上的字体 ( i.e. src: local('Arial'); )。如果找不到该字体,将会尝试其他来源,直到找到它。
  • font-weight,定义该字体的字重,如果所需字体符合描述,则采用本 font-face 所定义的字体。
  • font-style,定义该字体的样式。如果所需字体符合描述,则采用本 font-face 所定义的字体。

除此之外还有 font-variantfont-stretchunicode-range 等属性,可以参照 @font-face - CSS:层叠样式表 | MDN

@font-face 定义多字重字体

上文提到,虽然浏览器可以对常规字体进行强制加粗但视觉效果并不理想,但不同的样式又对应不同的字体文件,如何使用 @font-face 加载不同样式的子字体文件呢?答案是通过定义不同的字体样式来加载不用的字体文件,例如:

<html>
  <head>
    <style>
      @font-face {
        font-family: 'Source Han Serif';
        src: url(/font/SourceHanSerifCN/regular.ttf);
      }
      @font-face {
        font-family: 'Source Han Serif'; /* 同一个字体名称 */
        src: url(/font/SourceHanSerifCN/bold.ttf); /* 但不同的字体文件 */
        font-weight: bold; /* 根据字重应用不同的文件 */
      }

      .regular{
        font-family: "Source Han Serif";
        font-size: 30px;
        /* 常规字体,应用 regular.ttf 字体文件 */
      }
      .bold {
        font-family: "Source Han Serif";
        font-size: 30px;
        font-weight: bold;
        /* 加粗字体,应用 bold.ttf 字体文件 */
      }
    </style>
  </head>
  <body>
    <div class="regular">海内存知己,天涯若比邻。</div>
    <div class="bold">海内存知己,天涯若比邻。</div>
  </body>
</html>

显示效果如下:

image.png

类似地,还可以为斜体等字体效果单独指定字体文件。

如果你使用的字体有可变字体版本,那么只需要加载一个字体文件就可以把全部样式搞定了!

字体提取压缩

对于拉丁文语言来说,一个完整的字体文件通常不过几百 KB,因为只需要很少的一些字符就能完整地表达所有单词和句子。但对于汉字这种象形文字来说,每一个字符都需要单独的设计,因此完整的汉字字体包往往达到数十兆大小。这对于网络页面加载来说是十分不利的。

因此,在页面设计中如无必要往往不会对汉字正文字体使用自定义的远程字体。但在有些时候,我们为了页面设计的美观和各种文本的区分,可能需要对主页标题或重要信息使用特定设计的字体。在这种情况下,使用特殊字体的字符数往往很少,如果因此就引入一个数十兆的字体文件显然得不偿失。

应对这种情况,使用切图可能是一种解决方案。但切图不利于 SEO,且当字体内容频繁更新时需要依赖设计人员,十分不便。此时,可以考虑使用自动化的脚本将页面中使用到特殊字体的字符进行提取,然后重新压缩成一个只包含特定字符的字体包来取代完整的字体包,这将大大提高页面加载速度。

页面字体提取和压缩已经有一些第三方库可用,例如 font-snakefontminfont-spider 等。除此之外还有专门针对字体设计的 opentype.js,该库保持积极的更新和活跃的用户群体,我的主页主题 hexo-theme-tranquility 便使用了该库进行字体压缩。但需要注意,这些库目前都不支持可变字体的提取,所以当你进行字体提取时仍然可能需要加载多字重子字体文件。


参考资料

[1] PostScript[EB/OL]//Wikipedia. (2023-02-06)[2023-03-08]. https://en.wikipedia.org/w/index.php?title=PostScript&oldid=1...

[2] TrueType[EB/OL]//Wikipedia. (2023-02-14)[2023-03-08]. https://en.wikipedia.org/w/index.php?title=TrueType&oldid=113...

[3] OpenType[EB/OL]//Wikipedia. (2023-02-12)[2023-03-08]. https://en.wikipedia.org/w/index.php?title=OpenType&oldid=113...

[4] PETERCON. Recommendations for OpenType Fonts (OpenType 1.9) - Typography[EB/OL]. (2021-12-09)[2023-03-08]. https://learn.microsoft.com/en-us/typography/opentype/spec/re...

[5] 可变字体[EB/OL]//维基百科,自由的百科全书. (2021-08-19)[2023-03-08]. https://zh.wikipedia.org/w/index.php?title=%E5%8F%AF%E5%8F%98...

161 声望
0 粉丝
0 条评论
推荐阅读
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs40阅读 4.7k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco20阅读 2.1k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
你可能不需要JS!CSS实现一个计时器
CSS现在可不仅仅只是改一个颜色这么简单,还可以做很多交互,比如做一个功能齐全的计时器?样式上并不复杂,主要是几个交互的地方数字时钟的变化开始、暂停操作重置操作如何仅使用 CSS 来实现这样的功能呢?一起...

XboxYan21阅读 1.6k评论 1

封面图
「彻底弄懂」this全面解析
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在 哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在 函数执行的过程中用到...

wuwhs17阅读 2.4k

封面图
学会这些 Web API 使你的开发效率翻倍
随着浏览器的日益壮大,浏览器自带的功能也随着增多,在 Web 开发过程中,我们经常会使用一些 Web API 增加我们的开发效率。本篇文章主要选取了一些有趣且有用的 Web API 进行介绍,并且 API 可以在线运行预览。C...

九旬13阅读 1.6k

封面图
用了那么久的 SVG,你还没有入门吗?
其实在大部分的项目中都有 直接 或 间接 使用到 SVG 和 Canvas,但是在大多数时候我们只是选择 简单了解 或 直接跳过,这有问题吗?没有问题,毕竟砖还是要搬的!

熊的猫17阅读 1.5k评论 2

封面图
161 声望
0 粉丝
宣传栏