一、前言

基于Webpack的CSS Sprites实现方案,若是直接在html中调用雪碧图图标已经很方便,但是实际开发过程可能遇到需要在伪元素中使用雪碧图,或者需要hover切换另一个图标,这种情况下就无法在css中直接调用图标类名。这时,就需要css预处理器,当然不限于stylus,less、sass都是可以的,本文介绍stylus方案。

二、项目配置

1. 目录结构预览

+ node_modules
+ src               // 开发目录
    + css
        - icon.styl // webpack-spritesmith生成
        - index.styl
    + images
        - icon.png  // webpack-spritesmith生成
        + icon
            ..png
            ..png
    + js
        + main.js
+ dist              // 代码产出目录
    – index.html
    + js
        - main.js
    + css
        - index.css
    + images
        - icon.png
– package.json
– package-lock.json
- postcss.config.js
– webpack.config.js

2. 初始化项目

npm init

3. package.json

{
  "name": "cwwebpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --mode development"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^9.1.2",
    "clean-webpack-plugin": "^1.0.0",
    "css-loader": "^1.0.0",
    "cssnano": "^4.0.5",
    "file-loader": "^1.1.11",
    "postcss-loader": "^3.0.0",
    "stylus": "^0.54.5",
    "url-loader": "^1.1.1",
    "webpack": "^4.17.0",
    "webpack-cli": "^3.1.2",
    "webpack-spritesmith": "^0.5.4"
  },
  "dependencies": {
    "mini-css-extract-plugin": "^0.4.1",
    "stylus-loader": "^3.0.2"
  }
}

4. 安装相关modules

npm install

5. webpack.config.js

var path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const SpritesmithPlugin = require('webpack-spritesmith');

var templateFunctionStylPC = function(data) {
    var sharedRem = '.ico\n    display: inline-block\n    background-image: url("I")\n'
        .replace('I', data.sprites[0].image);

    var perSpriteRem = data.sprites.map(function(sprite) {
        return '.ico-N\n    width: Wpx\n    height: Hpx\n    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');

    return sharedRem + '\n' + perSpriteRem;
};

var templateFunctionStylMobile = function(data) {
    var sharedRem = '.ico\n    display: inline-block\n    background-image: url(I)\n    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\n    width: Wrem\n    height: Hrem\n    background-position: Xrem 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 sharedRem + '\n' + perSpriteRem;
};

module.exports = {
    entry: __dirname + "/src/js/main.js",   //唯一入口文件
    output: {
        path: __dirname + "/dist/js",       //打包后的文件存放的地方
        filename: "main.js"                 //打包后输出文件的文件名
    },
    watch: false,                           //开启自动编译
    module: {
        rules: [
            {
                test: /\.styl$/,
                use: [{
                    loader: MiniCssExtractPlugin.loader
                }, {
                    loader: "css-loader"
                }, {
                    loader: "postcss-loader"
                }, {
                    loader: "stylus-loader"
                }]
            },
            {
                test: /\.(png|svg|jp?g|gif)$/,
                loader: 'url-loader',
                options: {
                    limit: 8192,            //大于 1KB = 1024B = 8192字节 的图片正常打包,小于8192字节的图片以base64的方式引用
                    name: '../images/[name].[ext]'
                }
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(
            ['dist/css', 'dist/images'],    //打包前需要清除的文件夹
            {
                root: __dirname,            //根目录
                verbose: true,              //开启在控制台输出信息
                dry: true                   //关闭删除文件
            }
        ),
        new SpritesmithPlugin({
            src: {
                cwd: path.resolve(__dirname, 'src/images/icon'),
                glob: '*.png'
            },
            target: {
                image: path.resolve(__dirname, 'src/images/icon.png'),
                css: [
                    [path.resolve(__dirname, 'src/css/icon.styl'), {
                        format: 'function_based_template'
                    }]
                ]
            },
            customTemplates: {
                'function_based_template': templateFunctionStylPC
            },
            apiOptions: {
                cssImageRef: '../images/icon.png'
            },
            spritesmithOptions: {
                algorithm: 'binary-tree',
                padding: 2
            }
        }),
        new MiniCssExtractPlugin({
            filename: "../css/index.css",
        }),
    ],
};

6. 在src/images/icon中放置需要合成雪碧图的图标

7. index.styl

@import "icon.styl"

8. postcss.config.js

module.exports = {
plugins: [
require('autoprefixer')({
"browsers": [
"> 1%",
"last 2 versions",
"not ie = 8",
"android >= 4.0"
]
}),
/* require('cssnano')({
preset: 'default',//css压缩
}) */
]
}

9. 执行打包命令

npm start
  • icon.styl示例(由webpack-spritesmith生成)
.ico
    display: inline-block
    background-image: url("../images/icon.png")

.ico-btn-android-active
    width: 147px
    height: 59px
    background-position: -370px -250px

.ico-btn-android
    width: 147px
    height: 59px
    background-position: -370px -320px

10. index.styl(书写stylus代码)

@import "icon.styl"

.btn
    @extend .ico
    @extend .ico-btn-android
    &:hover
        @extend .ico
        @extend .ico-btn-android-active

11. 再次执行打包命令

npm start //若嫌每次手动打包麻烦的话,可以在webpack.config.js中开启自动编译watch: true

12. index.css(webpack打包后的文件)

.ico,
.btn,
.btn:hover {
  display: inline-block;
  background-image: url(../images/icon.png);
}

.ico-btn-android-active,
.btn:hover {
  width: 147px;
  height: 59px;
  background-position: -370px -250px;
}

三、stylus函数示例

1. 自动测量图片宽高
width-height-bg(img)
width: image-size(img)[0]
height: image-size(img)[1]
background: url(img)

.btn
display: inline-block
width-height-bg("../images/btn.png")

2. margin垂直居中
margin-center(img)
position: absolute
top: 50%
left: 50%
margin-left: (image-size(img)[0]/-2)
margin-top: (image-size(img)[1]/-2)

.item
margin-center("../images/item.png")

四、相关链接

五、错误记录

stylus相关

  1. expected “indent”, got “outdent”
    • tab和空格混用缩进,导致stylus编译出错
  2. expected “indent”, got “literal ../”
    • 图片路径需要双引号
  3. expected “indent” got “eos”
    • 混用了tab和空格,只能用其中一种
  4. 使用函数时别使用属性关键字,否则会报错

webpack相关

  1. 清除旧文件

  2. npm operation not permitted
    • 试试重启电脑(可能是什么进程锁住了文件),不行?再看看下面其他回答
    • 知乎

六、后记

或许以上还不是最优方案,但仍在自动化的边缘反复试探,在试探的过程中发现stylus的函数有点好用,配合图片使用,可以不用再去测量图片的宽高,还有其他特性慢慢摸索吧(ง’-̀’́)ง

分类: 前端

发表评论

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