Sprite画像を使用してページ表示を高速化
TL;DR
この方法ではSprite画像をレスポンシブに活用できなかったので改善しました
ページの表示を高速化するためにwebpack-spritesmithを用いて画像のスプライト化を行う。
複数の画像を1枚の大きな画像にまとめられた画像をスプライト化された画像というのは
具体的には以下のようなもの
https://gyazo.com/65f731c78a4e1ee35c40916cec9ebac7
画像が数十枚あるwebサイトの場合、画像の取得に時間がかかる
ブラウザによって同時接続数が決まっているため(chrome,Firefoxは同時接続数6)それ以上の画像の取得は順次行われる
50枚の画像を取得しようとすると50回サーバーにリクエストが投げられるため、負荷がかかる
複数の画像を1枚にまとめることで、サーバーとの通信回数を減らし、高速にwebページを表示することが可能
webpackでやると何がうれしいのか
なぜwebpackで行うか
1週間間隔で画像が追加されることがある
毎週画像をwebアプリを使ってスプライト化するのはしんどい
複数の画像サイズに適用できるスプライト画像作成サービスが無かった
例えば20x20サイズの画像4枚をつなげて 40x40にスプライト化する事は可能だったが、1枚の画像を10x10で表示したい場合はCSSを自分で書く必要があった。自分でCSS書くのはしんどい
コードとフォルダ構成
フォルダ構成
ico/iconset1 とico/iconset2 で別々のスプライト画像を作る。
スプライト画像の出力先は ico/sprite
スプライト画像スタイルの出力先は style
code: tree
├── app
│ ├── images
│ │ └── sprite.png
│ └── styles
│ └── bundle.css
├── package.json
├── ico
│ ├── iconset1
│ │ ├── icon1-1.png
│ │ ├── icon1-2.png
│ │ └── icon1-3.png
│ ├── iconset2
│ │ ├── icon2-1.png
│ │ ├── icon2-2.png
│ │ └── icon2-3.png
│ └── sprite
│ ├── iconset1-sprite.png
│ └── iconset2-sprite.png
├── style
│ ├── sprite-iconset1.styl
│ └── sprite-iconset2.styl
└── webpack.config.js
CSS出力サンプル
background-image 使用して、表示したい画像の位置まで background-position をずらして画像の表示を行う
code: sprite.css
.icon-image01 {
background-image: url("/img/sprite/champion-sprite.jpg")
display: inline-block
width: 64px
height: 64px
background-position: -64px 0px
}
vue.config.jsサンプル(webpack.config.js)
new SpritesmithPlugin() 内で設定する
code: vue.config.js
module.exports = {
configureWebpack: {
plugins: [
new SpritesmithPlugin({
src: {
cwd: path.resolve(__dirname, "src/ico/iconset1"), // バラバラの画像のフォルダパス
glob: "*.jpg"
},
customTemplates: {
function_based_template: spriteFunction
},
target: {
image: path.resolve(__dirname, "src/ico/sprite/iconset1-sprite.jpg"), //スプライト化画像の出力パス
css: [
[
path.resolve(__dirname, "src/style/sprite-iconset1.styl"), // 表示に必要なCSS(Stylus)の出力パス
{ format: "function_based_template" }
]
]
},
apiOptions: {
cssImageRef: "img/sprite/iconset1-sprite.jpg" // CSS内で使用されるスプライト化された画像のパス
}
}),
new SpritesmithPlugin({
src: {
cwd: path.resolve(__dirname, "src/ico/iconset2"), // バラバラの画像のフォルダパス
glob: "*.jpg"
},
customTemplates: {
function_based_template: spriteFunction
},
target: {
image: path.resolve(__dirname, "src/ico/sprite/iconset2-sprite.jpg"), //スプライト化画像の出力パス
css: [
[
path.resolve(__dirname, "src/style/sprite-iconset2.styl"), // 表示に必要なCSS(Stylus)の出力パス
{ format: "function_based_template" }
]
]
},
apiOptions: {
cssImageRef: "img/sprite/iconset2-sprite.jpg" // CSS内で使用されるスプライト化された画像のパス
}
}),
],
resolve: {
}
}
};
テンプレート
webpack-spritesmith では、テンプレートを用いて出力されるCSSを変更することが可能。
1つの画像群で1枚の画像を生成し、それを複数サイズ画像で表示したかったため、自作のテンプレートを用いた。
以下がそのコードとサンプル(出力されるのは Stylus 形式) 設定方法
設定用の配列を投げるとその設定を適用した Function が返ってくる。
これを config.jsのcustomTemplatesに設定する。
code:sample.js
const spriteFunction36s48m = spriteFunction([
{ size: 36, suffix: "s" },
{ size: 48, suffix: "m" }
]);
出力サンプル(Stylus)
code: style.css
.icon-champion
background-image url("/img/sprite/champion-sprite.jpg")
display inline-block
.icon-aatrox
width 64px
height 64px
background-position 0px 0px
.icon-ahri
width 64px
height 64px
background-position -64px 0px
.icon-aatrox-s
width 36px
height 36px
background-position 0px 0px
background-size: 288px 252px
.icon-ahri-s
width 36px
height 36px
background-position -36px 0px
background-size: 288px 252px
.icon-aatrox-m
width 48px
height 48px
background-position 0px 0px
background-size: 384px 336px
.icon-ahri-m
width 48px
height 48px
background-position -48px 0px
background-size: 384px 336px
テンプレート作成関数
関数のカリー化を使っている。カリー化を見た時にこれは使えるんだろうかって思った記憶があるが、今回使えてうれしかった。(内部の処理はともかく)Coolな構造で書けていると思う。
もっといい方法があれば、ぜひ教えてほしい。
code: template.js
const spriteFunction = sizeSettingList => data => {
// sizeSettingList: {size: number, suffix: string}[]
let stringBuilder = "";
stringBuilder = '.icon-A\n background-image url("/I")\n display inline-block\n'
.replace(
"A",
.split("/")
.pop()
.split("-")
.shift()
)
.replace("I", data.sprites0.image); const spriteHeight = parseInt(data.spritesheet.px.height);
const spriteWidth = parseInt(data.spritesheet.px.width);
stringBuilder += data.sprites
.map(sprite => {
return ".icon-N\n width Wpx\n height Hpx\n background-position Xpx Ypx\n"
.replace("N", sprite.name.toLowerCase())
.replace("W", sprite.width)
.replace("H", sprite.height)
.replace("X", sprite.offset_x)
.replace("Y", sprite.offset_y);
})
.join("");
for (const setting of sizeSettingList) {
const perSprite = data.sprites
.map(sprite => {
const scaledHeight = Number(setting.size) / sprite.height;
const scaleWidth = Number(setting.size) / sprite.width;
return ".icon-N-C\n width Wpx\n height Hpx\n background-position Xpx Ypx\n background-size: Apx Bpx"
.replace("N", sprite.name.toLowerCase())
.replace("W", sprite.width * scaleWidth)
.replace("H", sprite.height * scaledHeight)
.replace("X", sprite.offset_x * scaleWidth)
.replace("Y", sprite.offset_y * scaledHeight)
.replace("A", spriteWidth * scaleWidth)
.replace("B", spriteHeight * scaledHeight)
.replace("C", setting.suffix);
})
.join("\n");
stringBuilder += "\n" + perSprite;
}
return stringBuilder;
};
参考