如何解决网站字体文件加载过慢的问题?

本文最后更新于 2024年6月18日 下午

问题分析

自定义字体文件加载过慢,导致网站设定的字体迟迟无法显示。
由于我个人比较重视网站的整体设计,因此对于网站字体有着较高的要求。为了保证网站字体美观(与主题适配、方便阅读)、统一(在不同平台上均为统一字体,展现的效果一致),我选择了上传自定义字体文件(css中的@font-face方法)作为网站资源,并在用户访问网站时加载字体文件用于显示。相关css设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@font-face {
font-family: "Normal";
src: url("/font/Normal.ttf") format('truetype');
}
@font-face {
font-family: "Heavy";
src: url("/font/Heavy.ttf");
}

html, body, p {
font-family: "Normal", "新宋体", sans-serif, Georgia;
}

.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h4, h4, h5, h6, strong {
font-family: "Heavy", "新宋体", sans-serif, Georgia;
font-weight: bolder;
}

其中的Normal.ttf与Heavy.ttf均为思源宋体(分别是Semibold与Heavy)。
如此配置后,的确可以满足上述对于字体的“美观、统一”的需求,但出现了加载效率的问题。由于字体文件体积较大(均为约10MB),网站大部分内容加载出来后,字体仍然无法下载完成并进行显示,导致原有的设计效果无法正常呈现给用户。根据此问题,对网站字体提出新的需求——“快速”。
要实现“快速”的效果,大致有两个方向思路:1.加快加载速度(提高连接速度/字体文件并行加载)2.压缩字体文件体积。根据目前的个人能力水平,我选择从后者寻找方法。

解决方案

压缩字体文件体积的主要思路就是“去除无用,留下有用”。一个字体文件需要存储大量的数据以保证每一个字(无论是否生僻)都能有相对应的字形,然而网站中大概率不会把所有字都使用一遍。因此,如果可以根据网站的文字内容,留下字体文件中需要用到的字的数据并删除其余的冗余数据,就可以实现压缩体积的目的。最终实践呈现出的效果是,MB级别的字体文件可以被压缩至KB级别,大大缩小了字体文件,满足了“快速”的需求。
事实上,已经有现成的产品font-spider/font-spider-plus能够解决这类问题。但我在应用的过程中遇到了一些难以解决的问题(运行font-spider后,成功检索了需要用到的字但未能完成冗余数据删除工作),这个问题可能和字体文件格式/字体文件内部存储方式有关,因此放弃了现成的解决方案。

fonttools

压缩字体文件体积,其实就是取字体文件数据的一个子集。python的fonttools库提供了subset方法,能够实现取一个字体文件(如.ttf格式)的子集。fonttools可以直接作为命令在命令行中使用,其格式大致如下:

1
fonttools subset A  --text-file=B --output-file=C

其中,A为原始字体文件,C为压缩后的字体文件。B则是一个txt文件,他用来告知fonttools有哪些字需要被使用(A子集中有哪些元素)。
下面是一个简单的例子:

1
fonttools subset source.ttf  --text-file=use.txt --output-file=result.ttf

其中use.txt存储的内容为“你好世界”。那么该指令运行结束后就会生成一个result.ttf字体文件,这个文件只存储了“你、好、世、界”这四个字的字体数据,文件体积大大减小。

全站文件检索

上述内容已经为达成压缩字体文件的需求奠定了技术基础。下面考虑,如何得到use.txt这个文件?换言之,如何得到这个网站中需要用到的所有字是哪些?
hexo 生成的网站中展示给用户的是内容均为.html文件,并且所有网站需要使用到的资源(包括.html文件)均存储在public文件夹中,因此,我们只需要遍历整个public文件夹并读取所有的html文件内容,将其中使用到的字符进行登记、去重,最终保存到use.txt文件中即可。需要注意的是,public文件夹中存在着文件夹的嵌套(文件夹内包含文件夹),并且文件夹的深度并不唯一(可能存在多重嵌套),为了保证遍历到每一层、每一个文件夹中的每一个文件,可以选择使用深度优先搜索/广度优先搜索的方法进行遍历。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from pathlib import Path
import os

def is_chinese_char(char):
return '\u4e00' <= char <= '\u9fff'

def is_special_char(char):
return not char.isalnum() and not char.isspace()

def process_html_files(folder_path):
queue = [Path(folder_path)]
chinese_chars = []
special_chars = []
with open('result.txt', 'w', encoding='utf-8') as result_file:
while queue:
current_folder = queue.pop(0)
for item in current_folder.iterdir():
if item.is_file() and item.suffix == '.html':
with item.open('r', encoding='utf-8') as html_file:
content = html_file.read()
for char in content:
if is_chinese_char(char) and char not in chinese_chars:
chinese_chars.append(char)
elif is_special_char(char) and char not in special_chars:
special_chars.append(char)
elif item.is_dir():
queue.append(item)
for i in chinese_chars:
result_file.write(i)
for i in special_chars:
result_file.write(i)
for i in range(ord('z') - ord('a') + 1):
result_file.write(chr(i + ord('a')))
result_file.write(chr(i + ord('A')))
for i in range(10):
result_file.write(chr(i + ord('0')))


# 指定要遍历的文件夹路径
folder_path = '.../blog/public'
process_html_files(folder_path)

Heavy_font_path = '.../blog/public/font/Heavy.ttf'
Normal_font_path = '.../blog/public/font/Normal.ttf'
Temp_font_path = '.../blog/public/font/temp.ttf'


os.system("fonttools subset " + Heavy_font_path + " --text-file=result.txt --output-file=" + Temp_font_path)


if os.path.exists(Heavy_font_path):
os.remove(Heavy_font_path)

if os.path.exists(Temp_font_path):
os.rename(Temp_font_path, Heavy_font_path)

os.system("fonttools subset " + Normal_font_path + " --text-file=result.txt --output-file=" + Temp_font_path)


if os.path.exists(Normal_font_path):
os.remove(Normal_font_path)

if os.path.exists(Temp_font_path):
os.rename(Temp_font_path, Normal_font_path)

如何解决网站字体文件加载过慢的问题?
http://knnow.top/post/如何解决网站字体文件加载过慢的问题?.html
作者
氮氮NNU
发布于
2024年6月16日
更新于
2024年6月18日
许可协议