页面字体闪烁问题

现象

分析

WEB用到了自定义字体,DOM解析渲染时,字体还未加载完全,立即使用缺省字体显示,字体加载完成后,重新渲染替换

手段

明确了问题,就着手优化解决,改进点有以下几方面

样式抽离

当前WEB样式是打包进JS,每次是动态插入Style到header部分,所以较慢。另外JS变动实际上造成指纹【即内容hash】变化,样式就连代受影响,无法缓存,所以抽离还是有益于性能提升。

Webpack loader及plugins需要增加以下配置

1
2
3
4
5
6
7
8

{
test: /\.less$/,
use: [
{
loader: MiniCssExtractPlugin.loader
}]
}
1
2
3
4
5
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
filename: 'static/css/[name].[hash].css',
chunkFilename: 'static/css/[name].[hash].css'
})

预加载

注意

  1. 预加载,使字体不至于在CSS解析时才开始加载,提升字体资源加载的优先级
  2. 考虑当前开发的WEB,目标浏览器为Chrome,IE11+,Safari等,这里我只加载woff2,和woff,要知道体积大小上,woff2 < woff < tff,
  3. 因为woff2在IE下不支持,所以需要配置woff

贴下配置

1
2
3

<link rel="preload" as="font" type="font/woff2" href="static/font/Lato-Regular.woff2">
<link rel="preload" as="font" type="font/woff2" href="static/font/Lato-Bold.woff2">
1
2
3
4
5
6
7
8
9
10
11
@font-face {
font-family: 'Lato-Regular';
src: url('../static/fonts/Lato-Regular.woff2') format('woff2'), url('../static/fonts/Lato-Regular.woff') format('woff');
font-display: auto;
}

@font-face {
font-family: 'Lato-Bold';
src: url('../static/fonts/Lato-Bold.woff2') format('woff2'), url('../static/fonts/Lato-Bold.woff') format('woff');
font-display: auto;
}

注意如果字体加载跨域Link标签需要配置 crossorigin,比如crossorigin=”anonymous”,否则预加载即使成功,但安全考虑因此字体并不会真正应用,因此最终字体会因为预加载及CSS中的font-face加载两次。

字体文件体积

如上预加载可以分离CSS的解析与字体资源的加载,但字体文件过大也是个问题,为此进行如上配置,优先下载woff2,对IE才会下载woff,ttf不考虑。

关于不同格式字体的体积差异,比如labo-regular

  • woff-309KB
  • woff2-183KB
  • ttf-608KB

此外,还有2个手段可进一步缩小字体体积

  • base编码字体文件,也就是合并了字体与样式,减少请求。这里我并采用,但是个手段
  • 采用子集内嵌,因为字体文件中包含的字符编码并不是都用的上

字体显示策略

假如字体文件加载耗时在3秒可接受范围内,浏览器在字体加载还没完成时,可以以缺省字体显示内容,也可以先隐藏,3秒后,字体文件如果下载完成,则自定义字体显示文本,如果没有则,使用后备字体显示,等字体文件下载完成,再交换。

字体切换会出现闪烁效果,所以这里我将显示策略改为auto,,即走默认策略,3秒内还没加载完字体,文本不可见。

如使用swap,则字体加载的同时,会立即以默认字体展示,进而会有可视化看到字体变化的效果,一开始的问题便是这个原因。

但并不是swap就一定不好,具体使用哪个策略,视情况而定。

auto,block确保了不会出现字体变化的问题,但文本不可见的时间越长体验越差,所以要持续优化加载体验,比如CDN等。

相关术语

在网页性能这块实际上也是个重要的课题,比如就有以下这样的术语

  • FOUC (flash of unstyled text)

    网页渲染时,外部样式还没加载好,就以浏览器默认样式短暂地展示了部分内容,等到外部样式加载完成,又恢复正常的这个页面闪烁的过程

    今天的WEB如果处理好资源加载顺序,这个问题是完全可以避免的。

浏览器加载HTML,CSS,JS顺序

如上一开始的问题直接原因只是字体的显示策略需要调整,同时提升字体下载速度,但背后却牵引出浏览器在加载资源时的流程处理,于是,这里梳理下。

这里以一个实际的SPA index页面为例,浏览器加载到解析渲染的流程如下

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
<!doctype html>
<html class="no-js" lang="en" dir="ltr">
<head>
<base href="/" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>loading</title>
<meta name="description" content="Description for quotation">
<meta name="google" value="notranslate">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="preload" as="font" href="static/font/Lato-Regular.ttf">
<link rel="preload" as="font" href="static/font/Lato-Bold.ttf">
<link rel="shortcut icon" href="favicon.ico" />
<link rel="manifest" href="manifest.webapp" />
<link rel="stylesheet" href="static/css/antd.min.css">
<style type="text/css">
.browser-upgrade {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
</style>
<link href="static/css/vendors.2a1bd1c1e210b6a1697e.css" rel="stylesheet">
<link href="static/css/main.2a1bd1c1e210b6a1697e.css" rel="stylesheet">
</head>
<body>
<div id="root">
</div>

<script type="text/javascript" src="app/vendors.2a1bd1c1e210b6a1697e.chunk.js"></script>
<script type="text/javascript" src="app/main.2a1bd1c1e210b6a1697e.bundle.js"></script>

<script src="static/js/stomp.min.js" defer></script>
<script src="static/js/sockjs-1.0.0.min.js" defer></script>
</body>
</html>

有错,欢迎指出

补充

浏览器对CSS阻塞渲染有两种处理方式,要么等css解析完一起渲染,Chrome就是这么做的,但是会造成白屏;要么先把无样式的dom渲染出来等css解析好了再渲染一次,Firefox就是这么做的,但是会造成无样式内容闪烁

写在最后

不得不说浏览器相关的知识点还是挺琐碎,复杂的。。。共勉。

参考文档