話題の記事

身につけておきたいWebサイト高速化テクニック #6|HTTPリクエスト数削減テクニック02:CSS Sprite編

2013.08.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回はHTTPリクエスト数削減テクニック01「インラインイメージ編」でData URI Encodeを使ったインラインイメージについて紹介しました。この記事をより深く解説した記事をCodezineさまに寄稿したのでこちらも参考にしてください。

今回はHTTPリクエスト数削減テクニックの2つ目「CSS Sprite」について解説します。
すでに定番のテクニックとなっていますが何となく使っていることが多いと思うので復習と思って読んでみてはいかがでしょうか。これから覚える方にも向けて丁寧に解説したいと思います。

1,CSS Spriteとは

CSS Spriteとは、アイコンやナビゲーションなどのいくつかの画像を1つにまとめた画像(以降Sprite画像とする)を用意し、CSSのbackgroundプロパティを使って一部分のイメージだけを見せるテクニックです。代表的なSprite画像の例がYoutubeで使われているここの画像です。

Youtubeのスプライト画像

1.1,CSS Spriteの原理

原理についてもう少し詳しく紹介したいと思います。
CSS SpriteはCSSのbackground-imagebackground-positionプロパティを使って表示を制御します。Sprite画像の個々の画像の座標(左上の0:0を起点に個々の画像の起点を計算します)をbackground-positionに値を設定することで背景画像の位置を調整するというわけです。

CSS Spriteの概要

高解像度ディスプレイ(Retina Display)の場合

昨今スマートフォンへの対応も増えており画像も綺麗に表示したいという要望も多いと思います。高解像度ディスプレイへの対応も基本的な原理は通常のCSS Spriteの原理と同じです。解像度に合わせた画像(iPhoneであれば2倍)を用意することはもちろんですが、1つbackground-sizeというCSSプロパティを追加で指定します。

background-sizeプロパティは背景画像のサイズをCSS上でどのように扱うか指定するためのプロパティです。このプロパティを指定することで解像度をあまり意識すること無くbackground-positionの指定を行うことができます。

Retina Displayに対応したCSS Spriteの原理

1.2,CSS Spriteの作業と流れ

CSS Spriteにはこのような工程があります。
実際にやってみるとわかりますが、すべて手作業で行うと非常に手間なことが多いです。後ほど簡単に紹介しますがCSS Spriteは自動化するツールを使った方が良いでしょう。

  1. 1つにまとめる画像を選定する
  2. Sprite画像用のデザインファイルを作る
  3. 画像の配置を考える
  4. 配置を決定し座標をメモする
  5. CSS Sprite画像を書き出す
  6. background-imageにSprite画像を指定する
  7. background-positionで座標指定を行う

また、Sprite画像を作成するにあたり、少し頭を使います。
例えば、ある幅や高さが可変な要素に対してCSS Spriteを使う場合、Sprite画像内の要素を縦方向・横方向どちらか一方だけに並べたりどちらの方向にも対応するために斜めに配置したりなど描画に合わせて配置や画像間の余白を計算する必要があります。CSS3の普及により背景画像をアニメーションさせることもあるので、この場合も配置を考えなければならないでしょう。コーディングを経験しているデザイナー、コーダーであればこの点に注意しなければならないことがわかると思います。

2,CSS Spriteの記述方法

続いて、CSSにはどのような記述を行えばCSS Spriteが実現できるのかを紹介します。

2.1,HTMLの構造

横に並ぶ4つのナビゲーションがあると仮定します。HTMLの構造は以下の通りです。このナビゲーションの個々の要素に対してCSS Spriteを指定します。サンプルとして240px × 40pxが4 × 4で並んだ横幅960px縦幅160pxのSprite画像を用意しました。

Spriteイメージサンプル

<nav>
	<ul id="globalNav">
		<li class="item item01"><a href="#">01</a></li>
		<li class="item item02"><a href="#">02</a></li>
		<li class="item item03"><a href="#">03</a></li>
		<li class="item item04"><a href="#">03</a></li>
	</ul>
</nav>

2.2,CSSの指定

CSSでは.itemにSprite画像の指定を行い.item0Xに対してbackground-positionを使った座標の制御を行います。これだけでは、サイズを指定していないので内包するテキストなどによって背景がうまく表示されないので、横幅と高さを加えます。さらにアンカータグはインライン要素ですからこちらもdisplay: block;を加えてブロック要素にしましょう。

さらに、:hover:activeなどの疑似要素に合わせて座標をづらしていきます。

.item a {
        display:  block;
        width:    240px;
        height:   40px;
        
        background-image: url('sprite.png');
        background-repeat:        no-repeat;
}

.item01 a {
        background-position:        0px 0px;
}
.item01 a:hover {
        background-position:        0px -40px;
}
.item01 a:active {
        background-position:        0px -80px;
}
.item01.current a {
        background-position:        0px -120px;
}

座標指定は左上を起点にx軸・y軸の移動距離を指定するので値はマイナス指定になるので注意しましょう。

Sprite画像の座標指定について

このままでは内包するテキストや要素によってはみ出してしまう可能性があるのでoverflow: hidden;を指定しておきます。

.item a {
        overflow: hidden;
        display:  block;
        width:    240px;
        height:   40px;
        
        background-image: url('sprite.png');
        background-repeat:        no-repeat;
}

画像置換について

このままでは背景画像の上に文字が表示されてしまうので、文字を表示させたくない場合、画像置換というテクニックが使われています。昔からあるテクニックですが、数年前にはtext-indent: -9999px;を使った方法がよく使われていました。現在ではtext-indent: 100%;white-space: nowrap;を使う方がスマートです。基本的に画像置換を行っても問題ないと思いますが、Googleのコンテンツに関するガイドラインに隠しテキストと隠しリンクという項があります。この点には注意してください。

.item a {
        overflow: hidden;
        display:  block;
        width:    240px;
        height:   40px;
        
        background-image: url('sprite.png');
        background-repeat:        no-repeat;
        
        text-indent:    100%;
        white-space:  nowrap;
}

Retina ディスプレイの対応方法

Retinaディスプレイに対応するにはMedia Queriesを使ってデバイスに合わせてCSS Spriteの指定を振り分けます。このナビゲーションの場合、倍のサイズ1920px × 320pxとなります。これをMedia Queries内のbackground-imageに指定します。

.item a {
        overflow: hidden;
        display:  block;
        width:    240px;
        height:   40px;
        
        background-image: url('sprite.png');
        background-repeat:        no-repeat;
        
        text-indent:    100%;
        white-space:  nowrap;
}

@media (-webkit-min-device-pixel-ratio: 2),
       (min-resolution: 2dppx) {
	.item a {
        	background-image: url('sprite_x2.png');
        	background-size: 960px 160px;
	}
}

background-sizeが指定されていない場合、@media規則内のbackground-positionは1920px × 320pxの座標を前提として計算されますが、background-sizeを指定することにより通常時と同じbackground-positionで扱うことができます。

Retina ディスプレイに対応したCSS Spriteのサンプル

こちらのイメージではありませんが、実際に表示を確認できるサンプルとソースコードをこちらで公開しています。

3,CSS Spriteのメリット

例えばWebサイトのナビゲーションが5つあるとしましょう。その1つ1つのボタンに対してリンクの状態に合わせたnormal/hover/activeの3つ、さらに現在いる場所を表すcurrnetを足した4つの状態があるとすると合計で20個の画像になります。単純に考えると20回のHTTPリクエストが行われることになります。これだけHTTPリクエストが多いと、ブラウザの同時接続数の制限によりリクエストがブロックされ遅延の原因となります。このような場合にCSS Spriteを使って画像を1つにすることでHTTPリクエスト数を大きく削減できます。

3.1,個別に読み込んだときのウォーターホールチャート

同時接続数の制限によりWaitingの時間が増加し、Load Eventまでに時間がかかっています。

別々に10個の画像をロードした場合に同時接続数の制限によりWaitingの時間が増加し、Load Eventまでに時間がかかっています。

3.2,CSS Spriteを使ったときのウォーターホールチャート

Waitingが無くなり個別に読み込んだときよりLoad Eventが早く起きています。

同時接続数の制限に引っかからないのでファイルサイズが大きかったとしてもWaitingが無くなり個別に読み込んだときよりLoad Eventが早く起きています。

4,CSS Spriteのデメリット

CSS Spriteはパフォーマンスの向上も大いに見込め、非常に良いテクニックに見えますがいくつかのデメリットもあります。

4.1,一部の背景画像だけリピートすることができない

一般的に多くの画像をまとめているSprite画像で一部の画像をリピートしたくてもできません。無理にCSS Spriteで背景画像をリピートさせる必要はないのでリピートを行いたい場合はインラインイメージと併用すると良いでしょう。

4.2,Sprite画像を作る(作り直す)のに手間がかかる

例えばPhotoshopでデザインを行って切り出しを行う場合、CSS Spriteのためだけに別データを作る必要があります。「やっとできた!」と思ったところで画像を変更しなければならなくなった場合、Sprite画像に含める画像が多いほど配置調整と座標計算の手間がかかります。
このデメリットは解消する手段がはあります。後ほど紹介しますが最近はSprite画像をジェネレートできるWebサービスやSass / Compass(CSSをコンパイルできるメタ言語とフレームワーク)を使うことで大幅に手間を省くことができるようになりました。

4.3,メモリ消費に注意

これはCSS Spriteが悪い訳ではなく、10000px × 10000pxのような大きすぎるSprite画像を作ってしまうと余白や使っている色が増えファイルサイズも大きくなり、それがWebサイトの至る所で使われているとレンダリングにおいて大量のメモリを消費してしまうそうです(少し古い情報なのでブラウザによっては改善されているかもしれません)。リクエスト数を削減できたとしてもファイルサイズが大きくなりすぎてしまい画像のロードが遅くなり数秒間真っ白になってしまっては本末転倒です。Sprite画像のファイルサイズにも気を遣い大きくなりすぎないように分割しましょう。

4.4,Sprite画像をインラインイメージ化して良いのか

単純に考えればSprite画像をBase64エンコードしてインラインイメージとすれば完璧なんじゃないか!?と思ってしまうかもしれませんが基本的には併用しない方が良いでしょう。前回で解説しましたが、HTML,CSSのファイルサイズが大きくなりすぎてしまうと、DOMContentLoadedイベントの遅延の原因になります。

5,手間を省くツール

手間を省くにはなんと言ってもSass/Compassを利用するのがお勧めです。
ここでは解説を省かせていただきますがその他にもRuby系のパッケージにいくつかCSS Spriteを自動化できるものがあります。最終的にGruntを使って管理するのが良いでしょう。

5.1,Sass / Compassを使ったCSS Spriteの指定方法

Sass / Compassの使い方やCSS Spriteの指定方法は過去に書いているのでこちらを参考にしてください。

6,パフォーマンス計測

最後にいくつかのサンプル「CSSへのリンクによる埋め込み・インラインイメージ・CSS Sprite」でパフォーマンスを計測してみましょう。今回は高速な通信回線とモバイル通信回線(LTE)を使って2つのイベントが起こるまでの経過時間をチェックします。1つ目はDOMContentLoadedイベント(DOMの構築が終わった段階で呼ばれるイベント)、2つ目がLoadイベント(すべての読み込みが完了した段階で呼ばれるイベント)です。
計測したサンプルはこちらです。

6.1,ファイルサイズ

サンプルのファイルサイズがこちらです。CSS SpriteのSprite画像のファイルサイズが他のものよりも小さいですが Sass/Compass で作られたSprite画像のためこのサイズとなっています。Sass/Compassで作られたSprite画像は若干軽量化されて書き出されるのもうれしいところです

タイプ CSSへのリンク
による埋め込み
インラインイメージ CSS Sprite
HTML 640 B 647 B 673 B
CSS 916 B 15,398 B 733 B
画像 14,000 B
0 B 5715 B

6.2,DOMContentLoadedイベント

DOMContentLoadedイベントはどちらの通信回線も大きな差はみられませんでした。

高速な通信回線の場合

タイプ CSSへのリンク
による埋め込み
インラインイメージ CSS Sprite
平均値 169.5 ms 158.8 ms 171 ms
1(キャッシュ無し) 270 159 277
2 159 154 166
3 153 158 157
4 160 157 155
5 156 167 156
6 159 159 151
7 152 156 174
8 165 161 157
9 164 156 162
10 157 161 155

モバイル通信回線の場合

タイプ CSSへのリンク
による埋め込み
インラインイメージ CSS Sprite
平均値 895.2 ms 864.2 ms 868.4 ms
1(キャッシュ無し) 806 612 1,030
2 757 1,040 616
3 1,020 587 809
4 818 713 778
5 814 788 1,040
6 1,370 832 789
7 792 1,000 996
8 1,030 1,040 1,230
9 764 1,000 823
10 781 1,030 573

6.3,Loadイベント

Loadイベントはすべてのリソースの読み込みが完了した段階で呼ばれます。高速な通信回線ではSprite画像を使っていない「CSSへのリンクによる埋め込み」が遅く、インラインイメージとCSS Spriteが同程度のパフォーマンスとなりました。モバイル通信回線についても傾向としては同じようです。

高速な通信回線の場合

タイプ CSSへのリンク
による埋め込み
インラインイメージ CSS Sprite
平均値 550.5 ms 261.3 ms 280.9 ms
1(キャッシュ無し) 979 260 504
2 509 257 265
3 502 261 253
4 498 261 249
5 502 273 249
6 502 262 245
7 495 255 281
8 503 259 253
9 510 256 258
10 505 269 252

モバイル通信回線の場合

タイプ CSSへのリンク
による埋め込み
インラインイメージ CSS Sprite
平均値 1,217.9 ms 1,016.9 ms 1,031.8 ms
1(キャッシュ無し) 2,690 769 1,210
2 915 1,180 814
3 1,160 745 962
4 965 867 917
5 966 940 1,190
6 1,510 978 933
7 944 1,150 1,150
8 1,190 1,180 1,430
9 910 1,170 972
10 929 1,190 740

まとめ

CSS Spriteの紹介は以上となります。インラインイメージと比べるとCSS Spriteは積極的に使え、効果も高いテクニックです。
キャッシュも効きますし、HTMLのロードにも大きな影響も与えないため自動化さえできれば大きくリクエスト数を削減することができます。特にこのような画像には有効なテクニックです。

  • ボタンやナビゲーションなどのアフォーダンス毎に画像を切り替える場合
  • アイコンなどの細かい画像
  • セマンティックを考えたときにimgタグを使う価値のない装飾に使われている画像

次回はWebフォントアイコンについて紹介します。