去年十月份,为了在 Grunt 上快速合并精灵图,写了 img-sprite,允许基于 CSS 文件合并精灵图并更新样式。结果回到公司发现构建流程由 Grunt 迁移到了 FIS3,就把它丢一边了。

今年三月份,毕业设计使用 Webpack 作为构建工具的时候也想要 Auto Sprite 的功能,于是很粗糙地改造了 img-sprite 为 img-sprite-plugin,总算完成了任务。

然后到六月份的这段时间里,渐渐的发现 FIS3 自带的 fis-spriter-csssprites 不能够满足已有项目的需要,又有了重新写一个的冲动。

由于 img-sprite 无法适应到 FIS3 的编译流程中,干脆用 ES2015 重写,也就是 Emilia;此外,为了解决团队组帧动画的使用和 Canvas 一些使用场景上的痛点,又写了另一个模块 Lia,允许基于图片产出样式文件和精灵图。

于是,去年十月份考虑的 基于样式文件产出精灵图基于图片资源产出精灵图 两种方式分别通过 EmiliaLia 都实现了一遍,撒花撒花。

Emilia

Emilia 通过分析样式文件并识别其中的标记,如 url(a.png?__sprite),最终输出更新的样式文件和精灵图片。支持 rempx,包括数值转换。此外,这个模块被设计得更加容易适应不同的框架,如 FIS3 或 Webpack。

设计

编译的步骤其实跟 img-sprite 类似,几个关键步骤如下:

  1. 获取样式
  2. 分析样式
  3. 获取图片
  4. 合并图片
  5. 输出图片
  6. 更新样式
  7. 输出样式

获取样式

为了适应不同的框架,“获取”和“输出”相关的接口被设计为方便开发者重写。如在 fis3-spriter-emilia 中,重写输出样式文件的代码如下,仅替换了 FIS3 中 file 对象(保存引用在 file.node)的内容,而没有写到磁盘。

emilia.outputStyle = function(file) {
file.node.setContent(file.content);
};

分析样式

在“分析样式”阶段,通过 postcss 对样式文件进行遍历,抓取标记。而我之前在 img-sprite 的实现中,则是实现了 Traverse 用于深度遍历 css 转换出来的 AST,与 postcss 的做法感觉很相似。

合并图片

这里为了兼容 FIS3,Emilia 在整个编译过程中没有使用异步操作。为此在合并图片的过程中通过 child_process.execFileSync 阻塞事件循环。这或许是造成编译速度慢的原因之一。

比较

fis-spriter-emilia 与 fis-spriter-csssprite 比较如下:

  fis-spriter-emilia fis-spriter-cssspriter
编译速度 较慢,瓶颈在于坐标计算和合并图片
数值单位 支持任意单位,包括 rempx 只支持 px
数值缩放 支持缩放任意倍数,用于 rem 或 Retina 场景 不支持
内联样式分析 不支持 支持
图片标记 多个标记对应多套精灵图 sprites,对应单一精灵图
图片样式 不支持 repeat 支持 repeat 和 position
图片内联 支持 支持

由表格可见,fis-spriter-emilia 的优势体现在 rem的支持多套精灵图的产出,而这恰恰解决了我们团队在移动端的两个使用场景:rem 用于自适应;多套精灵图片用于图片懒加载。

速度慢的问题将在搞定手上的这一波需求之后进行优化,可能会完全重构计算和合并图片部分代码。

Lia

Lia 通过 sprite_conf.js 的配置命中图片资源,然后输出精灵图片和样式文件到指定文件夹。

我自己很喜欢 Lia 这个模块,原因是简单。合并组帧图只需要 lia here,初始化配置文件用 lia init,开发偶尔 lia 更新精灵图样式文件或者直接 lia -w 就完成了所有事情。

特点

  1. 支持 rem,支持数值转换
  2. 一次性输出多套精灵图片和样式文件
  3. 监听文件变化并且按需编译输出样式文件和图片
  4. 允许在当前文件夹下快速合并图片,多用于合并组帧动画的图片
  5. 支持自定义模板,以此支持输出 SCSS, LESS, JS, JSON 等等任意格式的文件

模块设计

主模块 lia.js 的代码量不多,提供了根据路径命中图片并输出精灵图和样式文件的功能。其他功能如快速合并图片、文件监听则通过相应的扩展实现,最终代码结构也就比较清晰。在这个基础上,也容易根据需要继续扩展其他功能。

文件监听

文件监听这部分功能基于 chokidar 实现。在文件改动的时候按需重新编译,其中做了几点优化:

  1. 一个时间段内(1000ms)的多个文件改动记录会被归并到一次变更检查中。
  2. 变更检查时,先重新匹配命中规则的图片,判断需要合并的图片数量是否变化;否则会拿到所有改动的文件判断是否从属与某个精灵图。有效的改动会触发所属的精灵图的重新编译。
  3. 在命中规则中过滤产出的精灵图,避免触发不必要的变更检查。

自定义模板

提供了 tmplwrap 两个参数,以支持产出 JS, JSON 等格式,可用于 canvas 动画场景中提供精灵图的坐标等信息。

小结

Emilia 和 Lia 同样处理 sprite 却是以两种不用的形式,满足了目前团队不同使用场景的需要。当然目前可优化的点还有很多,至少跟竞品的比较就有许多不足了,也有实践不足的原因导致一些潜在的问题没有被抛出来。

在写这两个模块的过程中我也会考虑这类型的 sprite 工具随着国内移动网络速度的提升以及在和 Lazyload,Icon Fonts 等优化手段的比较下是否还有合并图片的必要性。回头想想,作为一种开发和使用成本都较低的优化手段,能够解决目前存在的一两个痛点已经值得去尝试了。

然后 Emilia 其实是《Re:从零开始的异世界生活》的银发半精灵少女

最后感谢老大支持这种东西,给了一个多星期的时间没压需求下来😂