字蛛是一个 WebFont 智能压缩工具,它能自动化分析页面中所使用的 WebFont 并进行按需压缩,通常好几 MB 的中文字体可以被压缩成几 KB 大小。
字蛛主页:font-spider.org
字蛛从 2014 年 7 月诞生以来,时隔近两年,终于发布了 v1.0.0 正式版本,改进如下:
- 支持绝大多数的中英文 Truetype 字体
- 支持开源图标字体库 (New: v1.0.0新特性)
- 支持 CSS 伪元素解析,支持
content: "string"与content: attr(value)表达式 - 支持远程页面解析,并支持资源映射
- 支持四种样式规则:
</code>、<code><link></code>、<code>@import</code> 、<code>style=""</code> </li> <li>支持四种调用方式:命令行、Gulp、Grunt、JS Api</li> <li>性能、稳定性大幅提高</li> </ol> <h2>新特性:图标字体库压缩</h2> <p>得益于对 CSS 伪元素的支持,除了常规中英文字体压缩之外,v1.0.0 还带来了万众期待的——图标字体压缩支持,能够支持业界流行的开源图标字库。</p> <p>以 <a href="http://fontawesome.io">Font Awesome</a> 为例,它是一个典型的开源图标字体项目,目前包含有 628 个图标,并且还不断在添加中。虽然它已经做了很多优化,但字库的体积在移动端来说依然偏大,会影响页面载入速度。使用字蛛可以删除掉字体中没有用到的图标,将字体瘦身。例如一个使用 <a href="http://fontawesome.io">Font Awesome</a> 的示例页面:</p> <p><a href="https://cloud.githubusercontent.com/assets/1791748/15209091/cec2b91c-1861-11e6-920b-5fd2d130ff40.png" target="_blank"><img src="https://cloud.githubusercontent.com/assets/1791748/15209091/cec2b91c-1861-11e6-920b-5fd2d130ff40.png" alt="demo-icons"></a></p> <p>输入 <code>font-spider</code> 命令,启动字蛛进行字体压缩:</p> <p><a href="https://cloud.githubusercontent.com/assets/1791748/15213431/b856de98-1879-11e6-843e-71487797d4d7.png" target="_blank"><img src="https://cloud.githubusercontent.com/assets/1791748/15213431/b856de98-1879-11e6-843e-71487797d4d7.png" alt="font-spider"></a></p> <p>经过字蛛分析与压缩处理后,<a href="http://fontawesome.io">Font Awesome</a> 字体中只保留了页面所用到的 20 个图标,ttf 格式字体体积由 <strong>142</strong> KB 降为 <strong>6</strong> KB,如果再配合使用 Webpack 等前端工具将字体 Base64 编码后内嵌到 CSS 中,载入速度可以进一步提升。</p> <h2>爬虫实现原理</h2> <p>为什么字蛛能够找到字体中没有使用的字形数据?这里就涉及到对 HTML 与 CSS 的静态分析。</p> <h3>虚拟浏览器技术</h3> <p>字蛛 v1.0.0 版本使用了虚拟浏览器技术来实现 HTML 与 CSS 加载与解析,爬虫模块所依赖的浏览器相关 API 均为它提供。</p> <ul> <li>处理 <code><base></code> 标签以及资源定位</li> <li>加载 <code><link></code> 标签或 <code>@import</code> 语句导入的 CSS 文件</li> <li>处理 CSS Unicode 字符</li> <li>管理网络请求,处理资源映射配置</li> <li>支持 CSS3 选择器、样式表树、文本节点读取</li> </ul> <p>由于虚拟浏览器部分涉及到太多的东西且不是本文重点,所以本文将会略过这部分细节。这部分代码已经分离出来并开源,有兴趣可以去了解: <a href="https://github.com/aui/browser-x">https://github.com/aui/browser-x</a></p> <h3>操作样式语法树</h3> <p>字蛛是通过解析样式表语法树(CSSOM)来获得 WebFont 信息,在浏览器中可以通过 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Document/styleSheets"><code>document.styleSheets</code></a> 来访问 CSS 的语法树,遍历 CSS 规则的函数实现:</p> <div><pre><code>// 遍历 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); } }; })();</code></pre></div> <blockquote> <p>注:浏览器环境不允许访问跨域后的 CSSOM,但虚拟浏览器没有做此限制</p> </blockquote> <h3>查找字体</h3> <p>遍历样式表每一个规则,收集 <code>CSSFontFaceRule</code> 信息:</p> <div><pre><code>// 字体信息 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: '' }; } });</code></pre></div> <p>以如下页面作为示例:</p> <div><pre><code><!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: '——海子'; }面朝大海,春暖花开
得到 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": "面朝大海,春暖花开————海子"
}
}完整代码在线演示:jsfiddle.net/9ont96c4/2
至此,以上例子已经成功演示了字蛛爬虫查找字体、查找文本的工作原理。实际上 HTML 与 CSS 远比上面示例页面复杂,需要处理:
- 伪类选择器
-
font缩写 - 行内样式
- 完整的字体匹配算法
由于篇幅有限,上述细节部分可以参见字蛛爬虫模块源码。
相关链接