webpack+spritesmithでページの高速化、複数のスプライト画像とRetina対応について

フロントエンド系技術 フロントエンドエンジニア webpack spritesmith

Posted on 2017-01-31


webpack-spritesmithで複数のスプライト画像を作るってどうやるのか今ひとつわからなくてやってみたやり方の紹介。
やってみたら単純なことですね。SpritesmithPluginをnewしてpluginに複数設定してやればいい。 元となる画像をディレクトリわけて配置してそれぞれのディレクトリからスプライト画像を作る。
わける基準は例えばスプライト化した画像を最適化(減色をする)場合なんかは色味でわけると減色効率がよかったりする。
赤系は赤系だけでスプライト化するとかね。

ファイル構成はこんな感じ。

├── app
│   ├── images
│   │   ├── sprite01.png
│   │   └── sprite02.png
│   └── styles
│       └── bundle.css
├── node_modules
├── package.json
├── src
│   ├── images
│   │   ├── sprite_assets01
│   │   │   ├── sprite01.png
│   │   │   ├── sprite02.png
│   │   │   └── sprite03.png
│   │   └── sprite_assets02
│   │       ├── sprite04.png
│   │       ├── sprite05.png
│   │       └── sprite06.png
│   └── scss
│       ├── _sprite01.scss
│       ├── _sprite02.scss
│       └── style.scss
└── webpack.config.js

package.jsonの中はこんな感じ。

{
  "name": "01",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "webpack -w --config webpack.config.js"
  },
  "repository": {},
  "license": "MIT",
  "devDependencies": {
    "browser-sync": "^2.18.6",
    "browser-sync-webpack-plugin": "^1.1.3",
    "css-loader": "^0.26.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "node-sass": "^4.4.0",
    "path": "^0.12.7",
    "sass-loader": "^4.1.1",
    "style-loader": "^0.13.1",
    "webpack": "^1.14.0",
    "webpack-spritesmith": "^0.3.1"
  }
}

スプライト化する画像をこの場合は「spriteassets01」と「spriteassets02」にわけてる。 このディレクトリ内の画像がそれぞれsprite01.pngっていう名前でスプライト画像として出力されて、
scssも「sprite01.scss」と「sprite02.scss」っていう名前で出力される。

@import 'sprite01';
@import 'sprite02';

以下がwebpack.config.jsです。

var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var BrowserSyncPlugin = require('browser-sync-webpack-plugin');
var SpritesmithPlugin = require('webpack-spritesmith');

module.exports = {
  entry: {
    application: './src/scss/style.scss'
  },
  output: {
    path: './app/styles',
    filename: 'bundle.css'
  },
  module: {
    loaders: [
      {
        test: /\.css|scss$/,
        loader: ExtractTextPlugin.extract('style-loader', 'css-loader?minimize!sass-loader')
      },
      {
        test: /\.png$/,
        loaders: ['file?name=i/[hash].[ext]']
      }
    ]
  },
  resolve: {
    modulesDirectories: ['web_modules', 'node_modules', 'sprite_assets']
  },
  plugins: [
    new ExtractTextPlugin('bundle.css'),
    new BrowserSyncPlugin(
                  {
                    host: 'localhost',
                    port: 8081,
                    server: { baseDir: ['public'] }
                  }
                ),
    new SpritesmithPlugin({
      src: {
        cwd: './src/images/sprite_assets01/',
        glob: '*.png'
      },
      target: {
        image: './app/images/sprite01.png',
        css: './src/scss/_sprite01.scss'
      },
      apiOptions: {
        cssImageRef: '/images/sprite01.png'
      }
    }),
    new SpritesmithPlugin({
      src: {
        cwd: './src/images/sprite_assets02/',
        glob: '*.png'
      },
      target: {
        image: './app/images/sprite02.png',
        css: './src/scss/_sprite02.scss'
      },
      apiOptions: {
        cssImageRef: '/images/sprite02.png'
      }
    })
  ]
}

同系色を同じスプライトにした方が良いよってのもあるけど、Retina対応の画像と普通の画像って感じにわけてもいい。 Retina対応なんだけどスプライト画像といっしょに出力されるScssにはmixinも含まれるんだけどRetina対応じゃなかったりする。 で以下がデフォルトで出力されるScssのmixin。

@mixin sprite-width($sprite) {
  width: nth($sprite, 5);
}

@mixin sprite-height($sprite) {
  height: nth($sprite, 6);
}

@mixin sprite-position($sprite) {
  $sprite-offset-x: nth($sprite, 3);
  $sprite-offset-y: nth($sprite, 4);
  background-position: $sprite-offset-x  $sprite-offset-y;
}

@mixin sprite-image($sprite) {
  $sprite-image: nth($sprite, 9);
  background-image: url(#{$sprite-image});
}

@mixin sprite($sprite) {
  @include sprite-image($sprite);
  @include sprite-position($sprite);
  @include sprite-width($sprite);
  @include sprite-height($sprite);
}

@mixin sprites($sprites) {
  @each $sprite in $sprites {
    $sprite-name: nth($sprite, 10);
    .#{$sprite-name} {
      @include sprite($sprite);
    }
  }
}

これだとRetina用に書き出した2倍のサイズの画像だと変になる。 背景画像のサイズを半分にして位置も半分にするmixinを追加するといい。 出力されたScssに記述すると書き換えられるたびに追記しないといけないので自分で作ったScssに追記するのをおすすめする。

@mixin sprite-position-2x($sprite) {
  $sprite-offset-x: nth($sprite, 3) / 2;
  $sprite-offset-y: nth($sprite, 4) / 2;
  background-position: $sprite-offset-x  $sprite-offset-y;
}

@mixin sprite-2x($sprite) {
  @include sprite-image($sprite);
  @include sprite-position-2x($sprite);
  @include sprite-width($sprite);
  @include sprite-height($sprite);
  background-size: $spritesheet-width / 2 auto;
}

shigeki.takeguchi

渋谷の某ソーシャルゲームの会社でフロントエンドエンジニアとして働いてます。20世紀よりウェブ業界。気づいたらアラフィフ業界人。

By year

  1. 2017 (7)
  2. 2016 (23)
  3. 2015 (13)
  4. 2013 (15)
  5. 2012 (21)
  6. 2011 (34)

© 2012 shigeki.takeguchi