字蛛是一个 WebFont 智能压缩工具,它能自动化分析页面中所使用的 WebFont 并进行按需压缩,通常好几 MB 的中文字体可以被压缩成几 KB 大小。
字蛛主页:
字蛛从 2014 年 7 月诞生以来,时隔近两年,终于发布了 v1.0.0 正式版本,改进如下:
content: "string"
与 content: attr(value)
表达式<style>
、<link>
、@import
、style=""
得益于对 CSS 伪元素的支持,除了常规中英文字体压缩之外,v1.0.0 还带来了万众期待的——图标字体压缩支持,能够支持业界流行的开源图标字库。
以 Font Awesome 为例,它是一个典型的开源图标字体项目,目前包含有 628 个图标,并且还不断在添加中。虽然它已经做了很多优化,但字库的体积在移动端来说依然偏大,会影响页面载入速度。使用字蛛可以删除掉字体中没有用到的图标,将字体瘦身。例如一个使用 Font Awesome 的示例页面:
输入 font-spider
命令,启动字蛛进行字体压缩:
经过字蛛分析与压缩处理后,Font Awesome 字体中只保留了页面所用到的 20 个图标,ttf 格式字体体积由 142 KB 降为 6 KB,如果再配合使用 Webpack 等前端工具将字体 Base64 编码后内嵌到 CSS 中,载入速度可以进一步提升。
中文字体通常都有好几 MB 大小,直接嵌入网页中显然不太现实,利用字蛛压缩后可以大幅度的减少体积:
为什么字蛛能够找到字体中没有使用的字形数据?这里就涉及到对 HTML 与 CSS 的静态分析。
字蛛 v1.0.0 版本使用了虚拟浏览器技术来实现 HTML 与 CSS 加载与解析,爬虫模块所依赖的浏览器相关 API 均为它提供。
<base>
标签以及资源定位<link>
标签或 @import
语句导入的 CSS 文件由于虚拟浏览器部分涉及到太多的东西且不是本文重点,所以本文将会略过这部分细节。这部分代码已经分离出来并开源,有兴趣可以去了解:
字蛛是通过解析样式表语法树(CSSOM)来获得 WebFont 信息,在浏览器中可以通过 document.styleSheets
来访问 CSS 的语法树,遍历 CSS 规则的函数实现:
// 遍历 CSS 的规则
var eachCssRuleList = (function() {
// 遍历 CSSRuleList
function cssRuleListFor(cssRuleList, callback) {
var index = -1;
var length = cssRuleList.length;
var cssRule, cssStyleSheet;
while (++index < length) {
cssRule = cssRuleList[index];
// 导入的样式规则
if (cssRule instanceof CSSImportRule) {
cssStyleSheet = cssRule.styleSheet;
cssRuleListFor(cssStyleSheet.cssRules || [], callback);
// CSS 媒体查询规则
} else if (cssRule instanceof CSSMediaRule) {
cssRuleListFor(cssRule.cssRules || [], callback);
// 普通的规则
} else {
callback(cssRule);
}
}
}
return function(callback) {
var index = -1;
var styleSheetList = document.styleSheets;
var length = styleSheetList.length;
var cssStyleSheet, cssRuleList;
// 遍历 StyleSheetList
while (++index < length) {
cssStyleSheet = styleSheetList[index];
cssRuleList = cssStyleSheet.cssRules || [];
cssRuleListFor(cssRuleList, callback);
}
};
})();
注:浏览器环境不允许访问跨域后的 CSSOM,但虚拟浏览器没有做此限制
遍历样式表每一个规则,收集 CSSFontFaceRule
信息:
// 字体信息
var webFonts = {};
// 字体对应的元素列表
var elements = {};
// 找到 webFont
eachCssRuleList(function(cssRule) {
if (cssRule instanceof CSSFontFaceRule) {
var style = cssRule.style;
var family = style['font-family'];
var src = style.src;
// 保存使用此字体的所有元素列表
elements[family] = [];
// 保存字体信息
webFonts[family] = {
family: family,
src: src,
chars: ''
};
}
});
以如下页面作为示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>font-spider</title>
<style>
@font-face {
font-family: 'demo-font';
src: url('./demo-font.ttf');
}
h1.title {
font-family: 'demo-font';
}
h1.title::after {
content: '——海子';
}
</style>
</head>
<body>
<h1 class="title">面朝大海,春暖花开</h1>
</body>
</html>
得到 webFonts
:
{
"demo-font": {
"family": "demo-font",
"src": "url(\"file:///Users/aui/Documents/demo-font.ttf\")",
"chars": ""
}
}
利用 document.querySelectorAll()
来获取使用 WebFont 的字符:
// 获取当前节点所使用的 webFont
function matchFontFamily(cssRule) {
var style = cssRule.style;
var family = style['font-family'];
return webFonts[family];
}
// 将 fontFace 与元素、字符关联起来
eachCssRuleList(function(cssRule) {
if (cssRule instanceof CSSStyleRule) {
var selector = cssRule.selectorText;
var webfont = matchFontFamily(cssRule);
if (webfont) {
// 根据选择器来查找元素
var elems = document.querySelectorAll(selector);
Array.prototype.forEach.call(elems, function(element) {
// 获取元素的文本
webfont.chars += element.textContent;
// 将元素与字体关联起来
elements[webfont.family].push(element);
});
}
}
});
此时 webFonts
:
{
"demo-font": {
"family": "demo-font",
"src": "url(\"file:///Users/aui/Documents/demo-font.ttf\")",
"chars": "面朝大海,春暖花开"
}
}
// 处理伪元素,找到继承的 webFont
eachCssRuleList(function(cssRule) {
if (cssRule instanceof CSSStyleRule) {
var selector = cssRule.selectorText;
var pseudoName = /\:\:?(?:before|after)$/i;
if (!pseudoName.test(selector)) {
return;
}
// 查找伪元素所在的节点
selector = selector.replace(pseudoName, '');
var elems = document.querySelectorAll(selector);
// 获取伪元素 content 值
var content = cssRule.style.content.replace(/^["']|["']$/g, '');
for (var i = 0; i < elems.length; i ++) {
var elem = elems[i];
for (var family in webFonts) {
// 从伪元素自身不断冒泡,直到找到继承的字体
while (elem) {
if (elements[family].indexOf(elem) !== -1) {
webFonts[family].chars += content;
break;
}
elem = elem.parentNode;
}
}
}
}
});
此时 WebFont:
{
"demo-font": {
"family": "demo-font",
"src": "url(\"file:///Users/aui/Documents/demo-font.ttf\")",
"chars": "面朝大海,春暖花开————海子"
}
}
完整代码在线演示:
至此,以上例子已经成功演示了字蛛爬虫查找字体、查找文本的工作原理。实际上 HTML 与 CSS 远比上面示例页面复杂,需要处理:
font
缩写由于篇幅有限,上述细节部分可以参见字蛛爬虫模块源码。
相关链接
扫码关注w3ctech微信公众号
为什么我每次在打包字体图标后,字体图标就见了。
共收到1条回复