雪碧图自动化心得

作为前端攻城狮的我们,都应该知道,雪碧图(精灵图)存在的意义,主要是减少HTTP请求数和消除CSS背景“空闪”现象。但是手动制作/维护雪碧图的代价是很大的,我们需要不停的复制粘贴图层,并挪动位置,还要量坐标,劳神费力。。

至少是一年前就想要研究雪碧图自动化这件事了,由于本人太懒,拖到近期才完成相关实践。

早前,在没真正关注这件事情的时候,我以为,“先按照独立的背景图开发好后,再执行某个脚本任务,一切就水到渠成了”。直到真正要做这件事的时候,才发现我忽略了一个重要的问题。比如一个典型的场景,一个宽100px高100px的元素,应用了一个宽10px高10px的背景图片,并且是水平居中,垂直居中的。然后运行雪碧图自动拼接任务,生成了一个拼接过的雪碧图,密密麻麻的一片,找到前面的那个高10px宽10px的背景,旁边紧紧挨着其他不相干的背景,如果引用这张雪碧图,在不改变原来DOM结构的前提下,必定会“漏出”不相干的背景。其实就算是手动拼接雪碧图,如果不考虑周全,这种问题也很可能发生。附上我亲身经历的“悲剧”:

QQ20150614-1@2x

上图为构建前,使用独立背景开发的,表现很正常。再看:

QQ20150614-2@2x

上图为构建过的同一个元素的样式,就发生背景错乱了。。。其实错不在自动化工具,退一步讲,工具可以为背景和背景之间添加足够的空隙防止“漏出”不相干的背景(会增加雪碧图的size),但是它很难计算使用独立背景时的坐标,尤其是使用center,或百分比的情况,更是“无法”计算。

所以更合理的做法是,设置应用【需要自动拼接到雪碧图的背景】的元素的高宽与背景大小一致,然后通过移动此元素的位置,来达到“移动”背景的效果。雪碧图中的背景间隔可以为0,或适当的增加1-2像素,有效控制了雪碧图的size。

接下来,轮到雪碧图自动化工具上场了。彼时,还没意识到那个严重的问题,页面中的背景还都是独立的。

我最开始发现的是 https://github.com/Ensighten/grunt-spritesmith ,这也是star数最多的。迫不及待的在项目中安装,按照文档配置grunt任务,grunt sprite 回车,然后就是见证奇迹的时刻!打开目录,真的有“奇迹”,任务中指定的png都被拼装在一起了!而且还生成了一个css文件,内容是一批根据原来独立图片的名字命名的类选择器(可带前缀),并设置width,height,background-position等属性。一看到这个结果,就想到,这些选择器是需要被调用的,也就是说,这个构建过程应该是发生在开发页面之前的。而且,这些选择器肯定是要用在一个“额外的小元素”上的。大致上类似这种(摘自github上的文档):

1
2
3
4
5
6
.icon-fork {
background-image: url(spritesheet.png);
background-position: 23px 322px;
width: 32px;
height: 32px;
}

这跟我最初“天真”的想法不一样啊囧。

不爽,于是再去寻觅符合我想法的构建工具,还真找到了一个 https://github.com/laoshu133/grunt-css-sprite,是国人基于国外的工具二次开发的grunt插件。看了下文档,是属于后期构建的那种。于是再安装,配置,执行。的确是修改了我的css代码,再次查看页面,就发生了上面途中的悲剧。仔细观察了一下,它的原理是根据指定的css文件,正则匹配里面的背景设置代码,提取需要拼接的背景和选择器名,并将背景坐标删除,在css文件最后,把所有前面提取的class类名做一个汇总(逗号隔开),统一设置背景图片为为即将生成的雪碧图。页面的背景情况和css代码上图中也可以看出前后的变化。

这种方式还有一个弊端,如果在页面中设置了多背景,比如:

1
2
3
.xx{
background:url(../images/a.png) top center,url(../images/b.png) bottom center;
}

构建后就变成类似于

1
2
3
4
5
6
7
8
9
10
.xx{
background:top center,url(../images/b.png) bottom center;
background-position:-123px -321px;
}
...
.xx,
.yy,
.zz{
background-image:url(../images/spritesheet.png);
}

显然有问题,也许它应该忽略多背景样式。

当初也是折腾了一下午,把grunt-css-sprite 整合进项目构建流,才反过来发现,grunt-spritesmith的做法更加科学,更加无侵入式,风险更小。晚上折腾到11点多,终于把grunt-spritesmith整合进项目构建流。

总结

目前,我个人研究出的最合理的雪碧图自动化方式是

1.在开发页面前就调用grunt-spritesmith任务(即执行grunt sprite),自动生成sprite.css与spritesheet.png(二者路径与名字均可更改)

2.将sprite.css引入页面中

3.在页面中需要使用雪碧图中背景的地方,调用相应class即可

ps:个人建议使用:before伪类的方式生成class,这样调用背景图时可以少一个标签,附上我使用的配置与sprite.css模板:

任务配置:

1
2
3
4
5
6
7
8
9
10
11
sprite:{//合并sprite
all: {
src: [‘<%= yeoman.app %>/images//*.png’,
‘!<%= yeoman.app %>/images/_tmp//*.png’,
‘!<%= yeoman.app %>/images/spritesheet.png’,//不排除的话,这个图片会重复,越来越大,因为本身也是png
‘!<%= yeoman.app %>/images/yeoman.png’],//排除logo图片
dest: ‘<%= yeoman.app %>/images/spritesheet.png’,
destCss: ‘<%= yeoman.app %>/styles/sprites.css’,
cssTemplate:’sprite.css.handlebars’
}
}

sprite.css模板sprite.css.handlebars:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*本文件由grunt-sprite自动化生成,直接调用.iconsp.iconsp-&amp;lt;filename&amp;gt;即可调用相应背景*/
[class*="iconsp-"]{
position:relative;/*填上这个属性只是为了方便调用,如果有需要,请覆盖此属性*/
}
[class*="iconsp-"]:before{
content:"";
position:absolute;
display: block;
}
{{#sprites}}
.iconsp-{{name}}:before {
width: {{px.width}};
height: {{px.height}};
background-image: url({{{escaped_image}}});
background-position: {{px.offset_x}} {{px.offset_y}};
}
{{/sprites}}

生成的sprite.css大致为这样:
/本文件由grunt-sprite自动化生成,直接调用.iconsp.iconsp-<filename>即可调用相应背景/
class*=”iconsp-“{
position:relative;/填上这个属性只是为了方便调用,如果有需要,请覆盖此属性/
}

content:"";
position:absolute;
display: block;

}
.iconsp-add-dark:before {
width: 30px;
height: 30px;
background-image: url(../images/spritesheet.png);
background-position: -601px -170px;
}
```
页面中需要使用add-dark.png这个背景的元素,调用iconsp-add-dark这个class,然后指定.iconsp-add-dark:before的绝对定位坐标即可。

注意:

1.此sprite任务并不是只在最开始执行一次,你可以无限次执行,比如开发到一半,又新增了一些背景图片,你可以再次执行grunt sprite,新增了背景图片会自动合并进去,sprite.css也会增加新的class,但不保证【除了新增的背景,原来的背景坐标与原来一样】。所以,【千万不要】自己去量背景坐标。

2.grunt-spritesmith的高清图配置等配置尚未研究,请有兴趣的读者自行阅读官方文档。

3.你应该会遇到【有的背景(比如size很大的)并不需要合并入雪碧图】这样的场景,有两种方式可以实现:

1)直接在上面的grunt配置中排除,但这样就不通用了,拿到别的项目中需要更改

2)通过一个额外的文件,比如grunt-tasks-cfg.js,然后在Gruntfile.js中require(‘./grunt-tasks-cfg.js’),来读取配置,把排除列表配置并入之前的配置