一、前言

关于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链接:

  1. 中文|Webpack3之雪碧图插件(WEBPACK-SPRITESMITH配置简述)
  2. 英文|webpack-spritesmith官方配置文档
  3. 二倍图相关配置|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 被设计成一个可以高效传输许多小文件的协议,但当需要传输的文件数达到一定规模后,每个文件带来的额外开销也会积少成多,影响效率。所以至少在还有很长一段的时间内雪碧图还是很有必要的。

分类: 前端

发表评论

电子邮件地址不会被公开。 必填项已用*标注