异常现象

对于相同的文字「将来」

Unicode 对应:\u5c06\u6765

中文用户反馈在网页上看到了日文汉字,关注「将」
1.png
日文用户反馈在网页上看到了中文汉字,关注「将」
2.png

用户预期:

中文用户看到中文的汉字
2.png
日文用户看到日文的汉字
1.png

为什么会有这种中日用户看到错误的文字问题呢?

其实跟字符的 Unicode 有关系


首先看这两张图:
3.png
4.png
这些文字(一半日文、一半中文)很相似,都又有点不一样。但他们都有一个共同的特点:对应一个相同的 Unicode 码,尽管有不同的字形(形状)。

这种特殊情况涉及到的字符不多。从没人反馈说看到「おはようございます」有类似问题

那 Unicode 是什么?
简单讲就是:一个包含了世界上大部分字符的标准。每一个字符都分配了一个唯一的代码。

浏览器渲染文字的机制


我们再回到「中文文字异常」的问题,搞清楚这个问题需要先理解浏览器渲染文字的机制。

浏览器在渲染网页上的字符时,其实先看到的是字符的 Unicode 码。
5.png
我们用上面这个文字解释。

浏览器在遇到 U+6d45 这个字符时,应该渲染成什么形状呢?

有多个可能性。最终由浏览器决定使用的字体决定

字体(字体文件)是什么?
简单讲就是一个包含了 Unicode 码和对应字形的映射表

系统会内置很多字体,有一些网站也会选择使用 Web 字体。

中文有很多字体:

  1. 苹方
  2. 楷体
  3. 宋体
  4. 微软雅黑

日文也有很多字体:

  1. MS 明朝 MS ゴシック
  2. メイリオ EPSON 教科書体M
  3. HIragino Kaku Gothic

如果给「浅(U+6d45)」设置成日文字体,那就渲染出
6.png
如果给「浅(U+6d45)」设置成中文字体,那就渲染出
7.png

所以到这里,对于解决「中日文字异常」问题,下一步的思路应该是:只要给不同语言的用户设置不同的字体,问题就解决了。

确实可行,但可能没必要。

直接交给浏览器处理好了,浏览器能处理好这个问题。

因为浏览器可以自己检测用户的语言,然后选择对应语言的字体。

这有个前提:开发人员没有给文字设置字体(font-family没有设置任何内容),或者只是设置了通用字体(衬线、非衬线)

但是浏览器如何判断用户的语言呢?

  1. 首先会先查找网页的 lang 属性,如果没有的话 ⬇️
  2. 查找系统使用的语言

    测试过,不会使用浏览器的首选语言设置

所以 第一种解决方案(稍次) 就是开发人员:

  1. 不设置任何字体(但设置通用字体(衬线、非衬线)无妨)
  2. 不设置 HTML 的lang属性。相当于跟随系统的语言设置。

能解决这次的问题。效果就是:(对于上面那些中日相同 Unicode 的特殊文字)中文系统用户看到的都是中文字形,日文系统用户看到的都是日文字形。

但实际上,设置lang属性经常是有必要的。尤其是当产品做了多语言支持,已经支持了语言切换。那当用户切换语言时,除了需要使用不同的文案,还需要设置下 HTML 的lang属性来确保那些特殊文字能正确渲染成符合用户预期的字形。如果这时候不设置,就会看到:界面文案都改成了日文,但用户自己输入日文时却有时能看到中文字型,这是因为用户的系统字体是中文。

所以还是需要开发人员在网页刷新时根据浏览器的首选语言设置 HTML 的lang属性。

document.documentElement.lang = navigator.language || navigator.userLanguage;

或者当用户主动切换语言时,也顺便设置下 HTML 的lang属性。

所以解决中日文字异常的最佳方案是:「根据浏览器语言设置/用户选择界面语言,动态设置 HTML 的lang=xxx

字体的 fallback 机制


  1. 有些日文字体文件只包含日文文字,基本上其他文字(简体文字)都不包含。
  2. 有些日文字体文件除了包含日文文字,也包含基本上所有的简体文字(但中日相同 Unicode 的这部分文字肯定是被设计成了日文字形)
  3. 中文字体文件这边同理

当页面使用了日文字体(同时文件里基本没有简体字),但页面上又遇到了简体字的时候,怎么办?浏览器要怎么渲染这个 Unicode?

其实浏览器会按照顺序寻找包含这个简体字(Unicode)的字体,直到找到,然后渲染出来。

所以浏览器的 fallback 机制生效时,经常会遇到什么问题呢?字体混淆,不同字符有不同的字体,很不和谐,虽然中是中、日是日,但很别扭。

(图片只是混合了多种字体的中文,不是中日的,仅仅为了说明混了字体时的内容看着有多别扭。)
8.png
所以搞懂了 fallback 的原理后,其实也能明白:字体的 fallback 机制,跟我们这次的「中日文字异常,日本人看到了中华字体」不是一回事。

字体的 fallback 机制,只影响内容是否和谐,和文字正确显示与否无关。

常见问题


前端不指定任何字体,是否能解决这次中日文字异常的问题?

不一定解决问题。重点不是字体的设置,而是正确设置 lang 的问题。

使用 Web 字体 是否能解决问题?

同上,不能解决。

前端自己设置下日文字体,是否能解决问题?

不能解决。这样不管中文、日文用户在遇到上面的那种特殊文字时都会看到日文字体,对中文用户不友好。同理,只设置成中文字体,也不行。对日文用户不友好。

但有时候还是想给中文、日文设置一些漂亮的字体,那该怎么处理?
在正确设置lang的前提下,考虑追加类似 CSS:

html[lang='ja'],
html[lang='jp'] {
  font-family: 日文字体;
}
/* 包含 zh, zh-CN, zh-TW, zh-HK, zh-Hant, zh-Hans 等语言 */
html[lang^='zh'] {
  font-family: 中文字体;
}

提醒下:

  1. 其实上面把简体中文、繁体中文、粤语中文统一当成见简中处理也不是很合适。毕竟这次「中日字体异常」的问题,其实中繁那边也有同样的问题。
  2. 其实这次的「中日字体异常」问题,本质是:两个语言的一些文字公用了一些 Unicode,但有不同的字形设计。所以除了汉字圈的这些语言会有这种问题,拉丁字母圈同样会有这种问题。

其他相关知识


PingFang SC: 虽然是中文字体,但也包含了日文文字(但对于中日相同 Unicode 码的文字,字形被设计成了中文的字形)

日文的文字组成包含以下三个部分:

  1. 汉字
  2. 平、片假名
  3. 罗马字

9.png
其中汉字的来源起源于中文的汉字,但对于其中的一些字,日本人决定改造一下,设计成了不同的形状,含义和发音可能一致也可能不一致。


林水溶
1.2k 声望83 粉丝

全栈开发工程师