前端如何实现中文、英文、数字使用不同字体

前几天在知乎上回答了一个问题,这里记录备份一下。

在回答“前端如何实现中文、英文、数字使用不同字体?”这个问题之前,其实应该先明确中文、英文、数字包括哪些字。 对于中文而言,是否只包括中文汉字、汉字部首、汉语注音符号、中文常用标点符号,不包括日本汉字,不包括假名、谚文、喃字,英文是否只包括英语字母,不包括法语、德语、西班牙语、汉语拼音字母中特殊字母,数字是否仅限阿拉伯数字。

实现方式

以下是两种实现方式:

  1. 使用 CSS @font-face 规则和 unicode-range 属性。
  2. 使用 CSS :lang 伪类和 HTML lang 属性。

第一种方式

这种方式设置 unicode-range 属性时需要明确字符的 Unicode 范围,一般仅针对少量字符做处理,适合处理中文双引号展示、人民币符号、等宽字体显示时间等问题。

用法可参考如下 antd 的代码:

/* 来源自 https://github.com/ant-design/ant-design 目前版本已经移除该部分样式 */
@font-face {
  font-family: "Helvetica Neue For Number";
  src: local("Helvetica Neue");
  unicode-range: U+30-39;
}

@font-face {
  font-family: "Monospaced Number";
  src: local("Menlo"), local("Consolas");
  unicode-range: U+30-39;
}

@font-face {
  font-family: "Chinese Quote";
  src: local("PingFang SC"), local("SimSun");
  unicode-range: U+2018, U+2019, U+201c, U+201d;
}

body {
  font-family: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

其中指定 U+0030 - U+0039 的字符(即 0-9)使用 Helvetica Neue 字体,U+2018, U+2019, U+201C, U+201D(即单引号、双引号)使用苹方-简、中易宋体。

除此以外还可以参考 Google Fonts 的 CSS,如:

/* 来源自 https://fonts.googleapis.com/css?family=Open%20Sans:300,400,600,700&lang=en */

/* cyrillic */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 300;
  src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OVuhpKKSTj5PW.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* greek */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 300;
  src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OUehpKKSTj5PW.woff2) format('woff2');
  unicode-range: U+0370-03FF;
}

/* latin */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 300;
  src: local('Open Sans Light'), local('OpenSans-Light'), url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OUuhpKKSTjw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

其中为西里尔字母、希腊字母、拉丁字母指定了不同的字体。

第二种方式

这种方式基于语言代码(Language Code),在处理国际化、本地化时常用,适合处理中英文混排。

可参考中文排版需求页面的代码:

/* 来源自 https://w3c.github.io/clreq/local.css */
[lang=zh-hant] {
  font-family: 'PingFang TC', 'Noto Sans CJK TC', 'Heiti TC', 'Microsoft JhengHei', Helvetica, Segoe UI, Arial, sans-serif;
}

[lang=zh-hans] {
  font-family: 'PingFang SC', 'Noto Sans CJK SC', 'Heiti SC', 'DengXian', 'Microsoft YaHei', Helvetica, Segoe UI, Arial, sans-serif;
}
<!-- 来源自 https://w3c.github.io/clreq/ -->
<p its-locale-filter-list="en" lang="en">Each cultural community has its own language, script and writing system. The transfer of each and every writing system into cyberspace is a task of utmost importance for information and communication technology.</p>
<p its-locale-filter-list="zh-hans" lang="zh-hans">每一个文化群体都拥有独自的语言、文字、书写系统。将个别书写系统在虚拟空间再现,对文化资产的承继而言,是信息传播技术的重要责任。</p>
<p its-locale-filter-list="zh-hant" lang="zh-hant">每一個文化群體都擁有獨自的語言、文字、書寫系統。將個別書寫系統在虛擬空間再現,對文化資產的承繼而言,是資訊傳播技術的重要責任。</p>

其中不同语言的 HTML 内容使用了不同的 lang 属性,并在 CSS 中为不同 lang 指定不同字体。

除此以外,以常见的苹果公司中文首页为例:

/* 来源自 https://www.apple.com.cn/v/home/er/built/styles/main.built.css */
html {
  font-family: "SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif;
}

[lang]:lang(ja) {
  font-family: "SF Pro JP","SF Pro Text","SF Pro Icons","Hiragino Kaku Gothic Pro","ヒラギノ角ゴ Pro W3","メイリオ","Meiryo","MS Pゴシック","Helvetica Neue","Helvetica","Arial",sans-serif
}

[lang]:lang(ko) {
  font-family: "SF Pro KR","SF Pro Text","SF Pro Icons","Apple Gothic","HY Gulim","MalgunGothic","HY Dotum","Lexi Gulim","Helvetica Neue","Helvetica","Arial",sans-serif
}

[lang]:lang(zh-CN) {
  font-family: "SF Pro SC","SF Pro Text","SF Pro Icons","PingFang SC","Helvetica Neue","Helvetica","Arial",sans-serif
}

[lang]:lang(zh-HK) {
  font-family: "SF Pro HK","SF Pro Text","SF Pro Icons","PingFang HK","Helvetica Neue","Helvetica","Arial",sans-serif
}

[lang]:lang(zh-MO) {
  font-family: "SF Pro HK","SF Pro TC","SF Pro Text","SF Pro Icons","PingFang HK","Helvetica Neue","Helvetica","Arial",sans-serif
}

[lang]:lang(zh-TW) {
  font-family: "SF Pro TC","SF Pro Text","SF Pro Icons","PingFang TC","Helvetica Neue","Helvetica","Arial",sans-serif
}

其中的这段 CSS 同样是使用 :lang 伪类为不同语言、地区指定不同的字体。

最后,演示一下中英文混排,样式参见上面的 CSS:

<p lang="zh-hans">苹果公司联合创始人史蒂夫·乔布斯(<span lang="en">Steve Jobs</span>)在斯坦福大学2005年毕业典礼上的演讲中曾提到过一句名言“<span lang="en">Stay hungry, stay foolish.</span></p>

总结

这两种方式各有千秋,前者需要考虑中英文及数字的 Unicode 范围,后者需要为不同语言的 HTML 元素添加相应 lang 属性。

补充

上述两种实现方式比较精细,如果设计要求不高可以采用一种更简单粗暴的方法,利用浏览器字体的回退机制,将西文字体写在前面,中文字体写在后面,这样也可以实现字母数字渲染为西文字体,中文部分渲染为中文字体。

举两个例子:

.foo {
  /* 西文部分使用 'Segoe UI',中文使用 'Microsoft YaHei' */
  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}

.bar {
  /* 西文部分使用 'Helvetica Neue'、Helvetica,中文使用 'PingFang SC' */
  font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', sans-serif;
}

实际项目中使用时注意考虑不同平台字体支持情况,做好回退处理。

以等宽字体为例,西文部分使用 Consolas, Menlo, Monaco, 'Courier New',中文使用 'PingFang SC', 'Microsoft YaHei',最后再加上 monospace 兜底。

.mono {
  font-family: Consolas, Menlo, Monaco, 'Courier New', 'PingFang SC',
    'Microsoft YaHei', monospace;
}