weixin_39617215
weixin_39617215
2021-01-11 03:23

WebFont 智能压缩工具——字蛛 1.0.0 正式版发布-新增图标字体压缩

banner-img

字蛛是一个 WebFont 智能压缩工具,它能自动化分析页面中所使用的 WebFont 并进行按需压缩,通常好几 MB 的中文字体可以被压缩成几 KB 大小。

字蛛主页:http://font-spider.org

字蛛从 2014 年 7 月诞生以来,时隔近两年,终于发布了 v1.0.0 正式版本,改进如下: 1. 支持绝大多数的中英文 Truetype 字体 2. 支持开源图标字体库 (New: v1.0.0新特性) 3. 支持 CSS 伪元素解析,支持 content: "string"content: attr(value) 表达式 4. 支持远程页面解析,并支持资源映射 5. 支持四种样式规则: <style><link>` 、style=""` 6. 支持四种调用方式:命令行、Gulp、Grunt、JS Api 7. 性能、稳定性大幅提高

新特性:图标字体库压缩

得益于对 CSS 伪元素的支持进一步完善,除了常规中英文字体压缩之外,v1.0.0 还带来了万众期待的——图标字体压缩支持。

Font Awesome 为例,它是一个典型的开源图标字体项目,目前包含有 628 个图标,并且还不断在添加中。虽然它已经做了很多优化,但字库的体积在移动端来说依然偏大,会影响页面载入速度。实际项目中往往用不了这么多图标,使用字蛛可以删除掉字体中没有用到的图标,从而将字体瘦身。例如一个使用 Font Awesome 的示例页面:

demo-icons

输入 font-spider 命令,启动字蛛进行字体压缩:

font-spider-iconfont

经过字蛛分析与压缩处理后,Font Awesome 字体中只保留了页面所用到的 20 个图标,ttf 格式字体体积由 142 KB 降为 6 KB,如果再配合使用 Webpack 等前端工具将字体 Base64 编码后内嵌到 CSS 中,载入速度可以进一步提升。

中文字体瘦身

中文字体通常都有好几 MB 大小,直接嵌入网页中显然不太现实,利用字蛛压缩后可以大幅度的减少体积:

font-spider-webfont

爬虫实现原理

为什么字蛛能够找到字体中没有使用的字形数据?这里就涉及到对 HTML 与 CSS 的静态分析。

虚拟浏览器技术

字蛛 v1.0.0 版本使用了虚拟浏览器技术来实现 HTML 与 CSS 加载与解析,爬虫模块所依赖的浏览器相关 API 均为它提供。 - 处理 <base> 标签以及资源定位 - 加载 <link> 标签或 `` 语句导入的 CSS 文件 - 处理 CSS Unicode 字符 - 管理网络请求,处理资源映射配置 - 支持 CSS3 选择器、样式表树、文本节点读取

由于虚拟浏览器部分涉及到太多的东西且不是本文重点,所以本文将会略过这部分细节。这部分代码已经分离出来并开源,有兴趣可以去了解: https://github.com/aui/browser-x

操作样式语法树

字蛛是通过解析样式表语法树(CSSOM)来获得 WebFont 信息,在浏览器中可以通过 document.styleSheets 来访问 CSS 的语法树,遍历 CSS 规则的函数实现:

 javascript
// 遍历 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 信息:

 javascript
// 字体信息
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: ''
        };
    }
});

以如下页面作为示例:

 html



    <meta charset="UTF-8">
    <title>font-spider</title>
    <style>
        -face {
            font-family: 'demo-font';
            src: url('./demo-font.ttf');
        }
        h1.title {
            font-family: 'demo-font';
        }
        h1.title::after {
            content: '——海子';
        }
    </style>


    <h1 class="title">面朝大海,春暖花开</h1>


得到 webFonts

 JSON
{
    "demo-font": {
        "family": "demo-font",
        "src": "url(\"file:///Users/aui/Documents/demo-font.ttf\")",
        "chars": ""
    }
}

查找字符

利用 document.querySelectorAll() 来获取使用 WebFont 的字符:

 javascript
// 获取当前节点所使用的 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

 json
{
    "demo-font": {
        "family": "demo-font",
        "src": "url(\"file:///Users/aui/Documents/demo-font.ttf\")",
        "chars": "面朝大海,春暖花开"
    }
}

伪元素

 javascript
// 处理伪元素,找到继承的 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:

 json
{
    "demo-font": {
        "family": "demo-font",
        "src": "url(\"file:///Users/aui/Documents/demo-font.ttf\")",
        "chars": "面朝大海,春暖花开————海子"
    }
}

完整代码在线演示:https://jsfiddle.net/9ont96c4/2

至此,以上例子已经成功演示了字蛛爬虫查找字体、查找文本的工作原理。实际上 HTML 与 CSS 远比上面示例页面复杂,需要处理: 1. 伪类选择器 2. font 缩写 3. 行内样式 4. 完整的字体匹配算法

由于篇幅有限,上述细节部分可以参见字蛛爬虫模块源码

相关链接 - 字蛛:主页 - 字蛛:更新日志 - 字蛛: v1.0.0 接口文档 - 字蛛:grunt 插件 - 字蛛:gulp 插件 - 字蛛:迟到的中文 WebFont - 字蛛:虚拟浏览器技术——BrowserX - 思源黑体:简体中文 ttf 版本 - 思源黑体:繁体中文 ttf 版本 - Google:网页字体优化 - Baidu:fontmin

该提问来源于开源项目:aui/font-spider

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

9条回答

  • weixin_39620662 weixin_39620662 4月前

    請問除了靜態頁面 .html 可以這麼做之外 動態產生的內容像是 .jsx 這一類的也行嗎?

    点赞 评论 复制链接分享
  • weixin_39617215 weixin_39617215 4月前

    jsx 是不支持的,它无法用浏览器解析。下一版本计划加入模板片段支持

    点赞 评论 复制链接分享
  • weixin_39746869 weixin_39746869 4月前

    请问为什么运行命令之后无效? 只是打印了一行"Load ...."就结束了......

    点赞 评论 复制链接分享
  • weixin_39617215 weixin_39617215 4月前

    -H 你的问题我很难回答,请按照标准的提 BUG 方式完善你的问题,不要贴到无关问题下。

    点赞 评论 复制链接分享
  • weixin_39746869 weixin_39746869 4月前

    噢,不好意思,发错地方了。 我说那个我后来发现问题所在了.. 原来是路径中不能包含有中文.. 感觉这个在官网加句说明用起来会方便些 :)

    点赞 评论 复制链接分享
  • weixin_39580682 weixin_39580682 4月前

    模板片段求支持啊,用vue,angular单页没法用啊

    点赞 评论 复制链接分享
  • weixin_39522423 weixin_39522423 4月前

    请问前辈,如何在vue中使用~~~~

    点赞 评论 复制链接分享
  • weixin_39638305 weixin_39638305 4月前

    前辈,请问在国际化方案中如何压缩字体啊,我的字体在不同语言环境下的js文件中

    点赞 评论 复制链接分享
  • weixin_39617215 weixin_39617215 4月前

    字蛛仅仅用来处理静态页面,你可以使用 fontmin 的 API 来处理

    点赞 评论 复制链接分享

相关推荐