原文地址: https://segmentfault.com/a/1190000021484338
作者: Fw恶龙
本文首发于: 思否
一、前言
关于css sprites(雪碧图/精灵图)的几种实现方案可以参考浅谈 CSS Sprites 雪碧图应用。本文主要讨论基于webpack的css sprites实现方案。
由于使用webpack时会涉及到其他插件,没有相关基础的可以参考我之前的一篇文章使用Webpack对CSS文件进行后处理先进行配置,不过目录结构和该篇会有点差别。
二、 目录结构预览
+ node_modules
+ src // 开发目录
+ images
+ icon
..png
..png
+ js
+ main.js
+ dist // 代码产出目录
– index.html
+ js
- bundle.js
+ css
- sprite.css
- sprite.json
+ images
- sprite.png
– package.json
– package-lock.json
– webpack.config.js
三、 安装webpack-spritesmith插件
npm install webpack-spritesmith --save-dev
四、 webpack.config.js配置
var SpritesmithPlugin = require('webpack-spritesmith');
// 生成的雪碧图CSS文件模板自定义,也可以不配置直接使用默认的模板
var templateFunction = function (data) {
// PC端配置
var shared = '.ico { diaplay: inline-block; background-image: url(I); background-size: Dpx Hpx; }'
.replace('I', data.sprites[0].image)
.replace('D', data.sprites[0].total_width)
.replace('H', data.sprites[0].total_height);
var perSprite = data.sprites.map(function (sprite) {
return '.ico-N { width: Wpx; height: Hpx; background-position: Xpx Ypx; }'
.replace('N', sprite.name.replace(/_/g, '-'))
.replace('W', sprite.width)
.replace('H', sprite.height)
.replace('X', sprite.offset_x)
.replace('Y', sprite.offset_y);
}).join('\n');
// 移动端配置
var sharedRem = '.ico { diaplay: inline-block; background-image: url(I); background-size: Drem Hrem; }'
.replace('I', data.sprites[0].image)
.replace('D', data.sprites[0].total_width / 100)
.replace('H', data.sprites[0].total_height / 100);
var perSpriteRem = data.sprites.map(function (sprite) {
return '.ico-N { width: Wrem; height: Hrem; background-position: X Yrem; }'
.replace('N', sprite.name.replace(/_/g, '-'))
.replace('W', sprite.width / 100)
.replace('H', sprite.height / 100)
.replace('X', sprite.offset_x / 100)
.replace('Y', sprite.offset_y / 100);
}).join('\n');
return shared + '\n' + perSprite + '\n\n' + sharedRem + '\n' + perSpriteRem;
};
...
...
plugins: [
new SpritesmithPlugin({
src: {
cwd: path.resolve(__dirname, 'src/images/icon'), // 图标根路径
glob: '*.png' // 图标类型
},
target: {
image: path.resolve(__dirname, 'dist/images/sprite.png'), // 生成雪碧图的名称和路径
css: [
[path.resolve(__dirname, 'dist/css/sprite.css'), { // 生成CSS文件的名称和路径
format: 'function_based_template' // 模板配置,注意在customTemplates中配置对应名称的属性名
}],
[path.resolve(__dirname, 'dist/css/sprite.json'), { // 生成json文件的名称和路径,想看图片数据的可以配置该项
format: 'json_texture'
}]
]
},
customTemplates: {
'function_based_template': templateFunction // 上一项使用到的模板变量
},
apiOptions: {
cssImageRef: '../images/sprite.png' // 生成的CSS中引用的雪碧图路径
},
spritesmithOptions: {
algorithm: 'top-down', // 生成的雪碧图图标排列方式
padding: 1 // 图标的间隔
}
}),
new SpritesmithPlugin... //如果需要生成不止一张雪碧图则继续配置
],
4.1 webpack-spritesmith的具体参数设置参考1、2链接:
- 中文|Webpack3之雪碧图插件(WEBPACK-SPRITESMITH配置简述)
- 英文|webpack-spritesmith官方配置文档
- 二倍图相关配置|Webpack中雪碧图使用详解:由于目前没有该需求,暂时没有去了解,先Mark下。
4.2 spritesmithOptions参数详解
4.2.1 algorithm属性
- 'top-down': 从上到下排列
- 'left-right': 从左到右排列
- 'diagonal': 对角线排列,从左上到右下
- 'alt-diagonal': 对角线排列,从右上到左下
- 'binary-tree': 二叉树排列,先从左到右,后从上到下
- 但是还不太理解图标的排列顺序,比如从上到下,那么是如何判断哪个图标在第一个呢?
4.2.2 在源码中自定义排列需求(没有该需求的可以跳过这步)
- 需求:每个图标的间隔可以通过padding参数设置,这在PC端是没有问题的(当然如果用户缩放比例有问题的话,也会出现移动端同样的问题),但是在移动端会有该问题Retina屏下的CSS雪碧图,这篇文章的结论是图标间隔控制2px,但是在实际情况下可能2px不够,主要原因是移动端在使用rem单位进行计算时不同浏览器对小数点的不同处理方式,所以我们在排列图标最好将图标的起始位置放置于整十倍的位置上,如:10px、20px,这样使用background-position: 0 50px;时,即使是使用rem单位,也只会变成0 .5rem而不会出现0 49px变成0 .49rem的情况。
- 解决:修改源码 node_modules/spritesmith/src/smith.js
Spritesmith.processImages
- old
// Add our images to our canvas (dry run)
images.forEach(function (img) {
// Save the non-padded properties as meta data
var width = img.width;
var height = img.height;
var meta = {img: img, actualWidth: width, actualHeight: height};
// Add the item with padding to our layer
layer.addItem({
width: width + padding,
height: height + padding,
meta: meta
});
});
- new
// Add our images to our canvas (dry run)
images.forEach(function (img) {
// Save the non-padded properties as meta data
var width = img.width;
var height = img.height;
var meta = {img: img, actualWidth: width, actualHeight: height};
// chauncywu
// 用户传入图标间隔参数padding,规定两个图标的间隔至少为padding,然后下一个图标的位置以10为倍数的位置开始
// eg.图标1高度为96px,加上padding: 2px,那么图标2的开始位置为96+2=98,然后取整即100px,从100px位置开始
var layerWidth = width + padding;
var layerHeight = height + padding;
if(layerWidth%10 != 0) {
layerWidth = Math.ceil(layerWidth/10) * 10;
}
if(layerHeight%10 != 0) {
layerHeight = Math.ceil(layerHeight/10) * 10;
}
// Add the item with padding to our layer
layer.addItem({
width: layerWidth,
height: layerHeight,
meta: meta
});
});
五、生成雪碧图
webpack --mode development
六、后记
在研究雪碧图的过程中也有在思考在项目中雪碧图的必要性,经常看到网上说HTTP/2即将到来,压缩代码、文件合并、雪碧图都是多余的操作,但是看到这篇文章HTTP/2 下提高网站加载速度的资源打包指南,结论是:尽管 HTTP/2 被设计成一个可以高效传输许多小文件的协议,但当需要传输的文件数达到一定规模后,每个文件带来的额外开销也会积少成多,影响效率。所以至少在还有很长一段的时间内雪碧图还是很有必要的。