Sprite 的各种尝试
去年十月份,为了在 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,允许基于图片产出样式文件和精灵图。
于是,去年十月份考虑的 基于样式文件产出精灵图 和 基于图片资源产出精灵图 两种方式分别通过 Emilia 和 Lia 都实现了一遍,撒花撒花。
Emilia
Emilia
通过分析样式文件并识别其中的标记,如url(a.png?__sprite)
,最终输出更新的样式文件和精灵图片。支持rem
和px
,包括数值转换。此外,这个模块被设计得更加容易适应不同的框架,如 FIS3 或 Webpack。
设计
编译的步骤其实跟 img-sprite 类似,几个关键步骤如下:
- 获取样式
- 分析样式
- 获取图片
- 合并图片
- 输出图片
- 更新样式
- 输出样式
获取样式
为了适应不同的框架,“获取”和“输出”相关的接口被设计为方便开发者重写。如在 fis3-spriter-emilia 中,重写输出样式文件的代码如下,仅替换了 FIS3 中 file 对象(保存引用在 file.node)的内容,而没有写到磁盘。
|
分析样式
在“分析样式”阶段,通过 postcss 对样式文件进行遍历,抓取标记。而我之前在 img-sprite 的实现中,则是实现了 Traverse 用于深度遍历 css 转换出来的 AST,与 postcss 的做法感觉很相似。
合并图片
这里为了兼容 FIS3,Emilia 在整个编译过程中没有使用异步操作。为此在合并图片的过程中通过 child_process.execFileSync
阻塞事件循环。这或许是造成编译速度慢的原因之一。
比较
fis-spriter-emilia 与 fis-spriter-csssprite 比较如下:
fis-spriter-emilia | fis-spriter-cssspriter | |
---|---|---|
编译速度 | 较慢,瓶颈在于坐标计算和合并图片 | 快 |
数值单位 | 支持任意单位,包括 rem 和 px |
只支持 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
就完成了所有事情。
特点
- 支持
rem
,支持数值转换 - 一次性输出多套精灵图片和样式文件
- 监听文件变化并且按需编译输出样式文件和图片
- 允许在当前文件夹下快速合并图片,多用于合并组帧动画的图片
- 支持自定义模板,以此支持输出 SCSS, LESS, JS, JSON 等等任意格式的文件
模块设计
主模块 lia.js 的代码量不多,提供了根据路径命中图片并输出精灵图和样式文件的功能。其他功能如快速合并图片、文件监听则通过相应的扩展实现,最终代码结构也就比较清晰。在这个基础上,也容易根据需要继续扩展其他功能。
文件监听
文件监听这部分功能基于 chokidar 实现。在文件改动的时候按需重新编译,其中做了几点优化:
- 一个时间段内(1000ms)的多个文件改动记录会被归并到一次变更检查中。
- 变更检查时,先重新匹配命中规则的图片,判断需要合并的图片数量是否变化;否则会拿到所有改动的文件判断是否从属与某个精灵图。有效的改动会触发所属的精灵图的重新编译。
- 在命中规则中过滤产出的精灵图,避免触发不必要的变更检查。
自定义模板
提供了 tmpl
和 wrap
两个参数,以支持产出 JS, JSON 等格式,可用于 canvas 动画场景中提供精灵图的坐标等信息。
小结
Emilia 和 Lia 同样处理 sprite 却是以两种不用的形式,满足了目前团队不同使用场景的需要。当然目前可优化的点还有很多,至少跟竞品的比较就有许多不足了,也有实践不足的原因导致一些潜在的问题没有被抛出来。
在写这两个模块的过程中我也会考虑这类型的 sprite 工具随着国内移动网络速度的提升以及在和 Lazyload,Icon Fonts 等优化手段的比较下是否还有合并图片的必要性。回头想想,作为一种开发和使用成本都较低的优化手段,能够解决目前存在的一两个痛点已经值得去尝试了。
然后 Emilia 其实是《Re:从零开始的异世界生活》的银发半精灵少女
最后感谢老大支持这种东西,给了一个多星期的时间没压需求下来😂