CSSスプライト:今も役立つ場面と作り方

CSSスプライトのチュートリアルの大半は、今も2009年と同じ説明から始まります。スプライトはHTTPリクエストを減らすので、サイトを速くするというものです。その議論は、最新サーバーにはもうないHTTP/1.1の制約に基づきます。実際に今も使う根拠になるもの、ならないもの、そして正確な background-position 動くものを作るRetinaの計算。

CSSスプライトの実体

CSSスプライトは、多数の独立した画像を含む1つの画像ファイルと、そのうち1つだけを表示するCSSで構成されます。切り抜き処理もJavaScriptも使いません。要素には固定の width および height、シート全体が次の項目に設定されます background-image、そして background-position 要素の背後でシートを動かし、目的のアイコンを可視ボックス内へ置きます。ボックスは窓、スプライトシートは背後でドラッグする大きな紙です。

初めて使う人がつまずく1つの点は、オフセットが 負の値。窓をアイコンへ動かすのではなく、シートを動かしてアイコンを窓の位置へ合わせます。左上がシートの左端から96ピクセルにあるアイコンを表示するには、シートを96ピクセル左へ動かします。その値は background-position: -96px 0.

具体例として、24×24のアイコン5個を横1列に並べ、アイコン間に8ピクセルの隙間を設け、左から8ピクセル、上から8ピクセルの位置から始めるとします。シートは160×32になります。3番目のアイコンの左端は8 + 24 + 8 + 24 + 8 = 72ピクセル、上端は8ピクセルです。ルールは次のとおりです。

宣言機能
background-image: url("/sprite.png")要素の背後にシート全体を読み込みます
background-repeat: no-repeatシートの繰り返し表示と、端に他のアイコンが現れるのを防ぐ
background-position: -72px -8pxシートを左に72px、上に8px移動し、3番目のアイコンを原点に置く
width: 24px; height: 24px表示枠をちょうど1つのアイコンに限定し、残りを隠します
display: inline-blockspanのようなインライン要素で、幅と高さを適用できるようにします

次の2つの数値以外は変更しないでください background-position 同じファイルから別のアイコンが得られます。仕組みはそれだけです。

256×128のスプライトシートに64×64のアイコン8個が4×2のグリッドで並び、2行目の3番目のアイコンが枠で囲まれている。矢印はシートの左上から横に128ピクセル、下に64ピクセルを示し、その隣の64×64の要素にはbackground-position: -128px -64pxのルールでそのアイコンだけが表示されている
要素は固定サイズの窓です。負のオフセットで背後のシートを動かし、目的のアイコンが窓に見えるようにします。

率直に言うと、HTTP/2で計算が変わった

CSSスプライトは、特定の制限を解消するために考案されました。HTTP/1.1では、ブラウザーはオリジンごとに少数、通常6本のTCP接続を開き、各接続では一度に1つのリクエストしか扱えませんでした。40個のアイコンファイルは、6本の経路を通る40件のリクエスト待ちを意味し、それぞれに接続の確立や往復の待ち時間がかかりました。1ファイルにまとめると、順次処理する40件のリクエストを1件にできました。遅延の大きい接続では、これは微小な最適化ではなく、ページで得られる最大の改善であることがよくありました。

HTTP/2は多重化します。多数のリクエストが1つの接続を同時に共有し、HTTP層での先頭待ちによるブロックがなく、ヘッダーもリクエスト間で圧縮されます。スプライトが解決するために考案された特定の問題は、ほぼ消えました。2026年のチュートリアルが、リクエスト数を減らすからスプライトは速いとだけ説明するなら、それはもう存在しないウェブを前提に書かれています。

最適化する前に、実際に何を配信しているかを確認してください。 最新のCDNや現在のホスティングを使うサイトなら、ほぼ確実にすでにHTTP/2かHTTP/3です。ネットワークパネルを開き、「Protocol」列を追加して見てください。存在しない制約を最適化で取り除くのは、無駄な作業です。

では、なぜこの記事があるのでしょうか。「元の理由がなくなった」と「理由がまったくない」は同じではないからです。多重化しても残る実際の利点がいくつかあり、判断すべきなのは次の点です:

  • リクエストごとの負荷はゼロにはなりません。 多重化したリクエストは低コストですが、無料ではありません。それぞれヘッダー、キャッシュ検索、ブラウザーのリソーススケジューラーの枠を使います。5アイコンなら誤差程度ですが、200個の小さいアイコンなら測定でき、スプライトはそれを1項目へまとめます。
  • キャッシュ管理が簡単になります。 1ファイルならキャッシュ項目1つ、1つの Cache-Control 方針と、無効化するハッシュ付きファイル名は1つです。個別に版管理する200アイコンは、管理が必要なビルド・デプロイ対象になりますが、通常は管理されていません。
  • 一括のバージョン管理。 すべてのアイコンが一緒に配信されるため、3個だけが古いデザインのキャッシュから読み込まれ、残りは新しいといった、半端に更新されたアイコンセットにはなりません。デザインシステムの導入では、単なる整理上の利点ではなく、正しさを保証する実質的な性質です。
  • アイコン欠落のちらつきがない。 シートを読み込むと、すべてのアイコンが瞬時に使えます。個別読み込みではリクエストの完了ごとに1つずつ現れ、初期表示領域のツールバーやアイコングリッドという、最も邪魔な場所で目立ちます。
  • 遅延のない状態切り替え。 同じシートの別の場所にあるホバー状態やアクティブ状態の画像は、すでにダウンロードされています。個別のファイルでは、ホバー画像は最初のホバーまで取得されないことが多く、初めてボタンに触れた際に空白がちらつきます。これは以前からスプライトを使う強い理由の1つであり、HTTP/2でもこの利点は弱まりません。

CSSスプライトを使わない方がよい場合

信頼できる推奨には、適さない場合も含める必要があります。CSSスプライトには、そのような場合がいくつかあります。

  • 単色で拡大縮小するアイコンにはSVGが適しています。 インラインの <svg> または次から作るSVGスプライト <symbol> および <use> Retinaの計算なしで任意の寸法へ拡大縮小し、色を次から継承: currentColor。一般的なUIアイコンセットには、こちらのほうが適しており、スプライトシートがアイコンシステムでのシェアの大半を徐々に失ったのも、このためです。
  • アイコン1つにスプライトは不要です。 画像が1つだけなら、その画像を使ってください。スプライトは、何の利点もなく、座標管理とビルド手順を増やします。
  • 個別に変わるアイコン。 一括のバージョン管理には両面があります。1つのアイコンが変わるだけでシート全体のキャッシュが無効になり、全ユーザーがすべてを再ダウンロードします。頻繁に変わるアイコンセットはスプライトに適しません。
  • 色変更やテーマ対応が必要なもの。 ラスター形式のスプライトには色が固定されています。ダークモード、ブランド別テーマ、状態ごとの色には、別のシートか壊れやすいフィルターの工夫が必要です。これはSVGの最も明確な構造上の利点です。
  • フォントサイズに応じて変わる必要があるアイコン。 スプライトはピクセル寸法に固定されます。アイコンを次に合わせて拡大縮小する必要があるなら em 文字と一緒に変えるなら、ベクター方式は対応できますが、スプライトでは苦労します。

スプライト、SVGスプライト、インラインSVG、アイコンフォント

方法きれいに拡大縮小CSSで色変更リクエストキャッシュアクセシビリティ
CSSスプライト(ラスター)いいえ — 固定ピクセルなので2xのシートが必要いいえ、フィルターによる工夫のみ全アイコンに1つ優秀、長期間使う1ファイル背景画像は支援技術から見えないため、テキストラベルが必要
SVGスプライト(symbol + use)はい、任意のサイズはい、currentColorで指定全アイコンに1つ優秀、1ファイルDOM内にあり、titleとARIAに対応
アイコンごとのインラインSVGはい、任意のサイズはい、CSSで完全に制御ゼロ。HTMLに埋め込みなし — 各ページで再送信最良、完全にDOM内に存在
アイコンフォントはい、font-sizeに追従はい、colorで指定1つのフォントファイル良好低い — 字形が読み上げられたり、代替フォントに置き換わる

表は点数表でなく判断として読んでください。旗、ロゴ、イラストのバッジ、アプリアイコンのスクリーンショットなど、多色のラスター画像は合理的にSVGスプライトにはできません。その領域ではCSSスプライトは古い方法でなく、今も正しい答えです。

1つを作成する

手順は4段階で、手作業で細かく調整する必要があるのは3段階目だけです。

  1. アイコンを集めてください。 最終表示サイズ、またはその2倍で書き出してください。Retinaの節を参照できます。できるだけサイズを揃えると、均一なグリッドによって座標計算が簡単になり、CSSも規則的になります。
  2. 1枚のシートへ配置してください。 単純な1列配置や固定グリッドは把握しやすく、サイズが異なる場合はパッカーのほうが空間を効率よく使えます。アイコン間には隙間を残してください。
  3. 座標を読み取ってください。 各アイコンには、シートのピクセル単位でx、y、幅、高さが必要です。画像エディターで手作業で測るのが、スプライト作業で問題が起こるところです。1ピクセルの読み違いで隣のアイコンの細い一部が現れ、理由を教えてくれるものはありません。
  4. CSSを書く。 共有の基本ルール1つと、各アイコンの小さいルール1つ。

共通の基本ルールは見た目以上に重要です。すべてのアイコンには同じものが必要です: background-image, background-repeat: no-repeat、そして display: inline-block。次を繰り返すと background-image 50個のルールにURLがあっても、ブラウザーは1回だけ取得するため性能上の問題ではありません。しかし保守の問題です。キャッシュ更新でシート名を変えると、1か所ではなく50か所の編集が必要になります。そこで、共有事項は基本クラス、位置とサイズだけをアイコン別クラスに置きます:

ルール目次
.spritebackground-image, background-repeat: no-repeat, display: inline-block
.sprite-searchbackground-position: -8px -8px; width: 24px; height: 24px
.sprite-settingsbackground-position: -40px -8px; width: 24px; height: 24px

マークアップでは、次のようになります <span class="sprite sprite-search"></span>。Sassでは、同じ考え方を通常はプレースホルダーとして表現します。 %sprite-base、各アイコンのルールには次を使って取り込みます @extend — コンパイル出力では宣言を繰り返さず1つのグループ化セレクターになり、マークアップはアイコンごとに1クラスで済みます。

背景画像はスクリーンリーダーには認識されません。 スプライトのアイコン自体は支援技術に何も伝えません。そのため、アイコンだけのボタンなど、単独で意味を持つアイコンには実際のテキストラベルが必要です。要素内の視覚的に隠したテキスト、または aria-label ボタンに設定します。既存文字の横の装飾アイコンには不要です。

RetinaとHiDPI:background-sizeの工夫

大半のガイドが省略または誤解する部分です。1xで描いたラスタースプライトは2x画面でぼやけます。対策はオフセット変更ではなく、2倍解像度で詰め、CSSに実寸の半分のシートサイズを伝えることです。

先ほどの例で、書き出し時にすべてを2倍にします。アイコンは実ピクセルで48×48、隙間は16、シートは実ピクセルで320×64になります。次に設定するのは background-size: 160px 32px — 実際のシート寸法の正確な半分。ブラウザーは全シートを160×32 CSSピクセルの座標空間へ縮小し、HiDPI画面では追加の細部を端末の物理ピクセルへ対応づけます。

利点は、CSSのほかの数値すべてを元の1x座標系のままにできることです。3番目のアイコンは引き続き background-position: -72px -8px と width: 24px; height: 24px、変更せず使えます。ファイル内の実際のピクセル位置が144, 16であっても同様です。オフセットはCSSピクセル単位で1回だけ計算し、1つの background-size 基本クラスの宣言で、全アイコンの密度の対応づけを一度に行います。

ここから導かれる2点を明示しておきます。まず、 background-size アイコンごとに繰り返さず共通の基本ルールに置きます。次に、強い理由がなければメディアクエリで2シートを配信しないでください。2xシートを半分で表示すると1x画面でも正しく見えるため、ファイルが大きくなる代わりに、1シート・1ルールで両方をカバーできます。十分圧縮したアイコンシートは通常小さいので、トレードオフに価値があります。また1.5や2.5などの小数の端末ピクセル比という中間領域も回避できます。メディアクエリの切り替えはどちらかを選ぶ必要があり、一方が不適切になるからです。

ホバーと状態のバリエーション

対話的な要素の典型的なスプライト配置は、列ごとにアイコン、行ごとに状態を置くグリッドです。1行目が通常、2行目がホバー、3行目が押下または無効です。列は移動しないので、状態変化はY方向だけの移動になり、Xオフセットはそのままです。

24pxアイコンと8pxの隙間なら、1行目はy = 8、2行目はy = 40です。3番目のアイコンの通常ルールは background-position: -72px -8px ホバーのルールは background-position: -72px -40px。縦方向の間隔を変数として設定すると、例えば --sprite-row: 32px — 基本クラス上でホバーを一度だけ表すには、 calc() アイコンごとのX変数と組み合わせ、各アイコンに2つ目のルールを書かずに済みます。

ここではスプライトが実際に個別ファイルより優れています。通常状態とともにホバーのピクセルも届いているので、初回ホバーが即座です。状態を別々にせず同じシートに置くべき理由でもあります。別のホバーシートにすると、初回ホバー時のリクエストによるちらつきという、配置が防ごうとした問題が戻ります。

余白と、ゲームよりにじみが弱い理由

画像を隣接して詰めると、サンプラーがアイコンの境界を越え、隣の色を取り込む可能性があります。ゲームエンジンでは、縮小、ミップマップ、フィルタリング、任意のサブピクセル変形で描くため、常にある既知の問題です。

CSSスプライトが使われる環境は、より穏やかです。ミップマップはなく、背景は通常、整数の位置で合成され、変形なしの正確な1:1なら、サンプリングは境界をまったく越えません。ただし「穏やか」は「起こらない」ではなく、問題が起こる場所は予測できます。端末のピクセル比が1.5、2.25、3のような整数でない値の場合、ブラウザーのページズームが半端な割合の場合、そして任意の transform: scale() 親要素に設定し、 background-size Retinaの節の縮小などです。いずれもアイコンの縁を端末ピクセルの非整数境界へ置くため、合成処理が補間する必要があります。

対策は同じで、低コストです。すべてのアイコンの間とシートの外周に、1xで2〜4ピクセル、2xシートなら4〜8ピクセルの小さな透明な隙間を残します。ファイルサイズの増加はわずかで、「このアイコンの左に薄い線があり、自分のノートPCでだけ出る」という種類の不具合報告を一掃できます。押し出し、ミップマップのにじみ、2の累乗の寸法という、より深い問題のゲームエンジン版はこちら: スプライトアトラスのパッキング:余白、2の累乗、色のにじみ.

よくある失敗のパターン

  • 端に隣のアイコンが見える。 要素のボックスがアイコンより大きく、窓からその先のシート内容が見えています。幅・高さが間違っているか、要素のpaddingがボックスを広げています。次を確認してください: box-sizing: border-box 使われているか確認してください。宣言した幅に含まれるものが変わるからです。
  • background-repeat: no-repeatの指定忘れ。 標準設定は repeat、そのためシートが繰り返され、他のアイコンの断片が枠内に現れます。アイコンがシートの原点近くにあり、ほぼ正しく見える場合は、この問題を見落としがちです。
  • 寸法のないspan。 インライン要素は次を無視します width および height 完全に。そのため空の <span> スプライトクラス付きでは寸法がゼロになり、何も表示されません。設定するのは display: inline-block (または block、あるいはflexアイテムにする)を基本クラスに指定します。
  • 再構築後も古いシートが残る。 スプライトを詰め直すと座標が変わりますが、再訪問者は新しいCSSに対して古いシートをキャッシュしている場合があり、自分には完璧でも相手には全アイコンが微妙に違います。再構築ごとにファイル名を変更してキャッシュを更新します(内容のハッシュ、 sprite.a1b2c3.png)を使い、クエリ文字列やユーザーによる強制再読み込みには頼らないようにします。
  • 半ピクセルのオフセット。 2xシートのアイコンサイズ、隙間、開始余白が奇数なら、半分にした後のオフセットが端数になり、それが端のにじみを起こします。2xシートのすべての寸法を偶数にしてください。

よくある質問

2026年にCSSスプライトは時代遅れですか?

元の根拠は時代遅れですが、技術はそうではありません。HTTP/2とHTTP/3では、リクエスト数の削減だけでは有力な理由になりません。残るのは、多くの小さなラスター画像を持つアイコンセットの用途です。1つのキャッシュ項目、不可分なバージョン管理、個別アイコンの読み込み時の出現を防げること、即座のホバー状態が今も利点になります。ただし、単色UIアイコンではSVGが実際にラスタースプライトを置き換えています。2026年にその用途で使うのは、より悪いツールを選ぶことです。

CSSスプライトとSVGスプライト、どちらを使うべきですか?

配信方法ではなく、画像の性質で決めてください。アイコンがフラット、幾何学的、単色または2色なら、SVGスプライトを使います。2xのシートなしで拡大縮小でき、次で色を変えられます currentColor、支援技術からアクセスできるDOM内に存在します。画像が写真、グラデーションの多いもの、または旗・商品のサムネイル・プラットフォームのロゴ・ピクセルアートのような実際に多色のラスター画像なら、SVGに利点はなく、ラスター形式のCSSスプライトが適切です。

シートはどれほど大きくなると問題になりますか?

2つの別の上限があります。実用上、シートは全アイコンの描画を妨げるため、初回描画を遅らせるほど大きいなら利点がなくなっています。数百KBの前半程度に抑え、管理画面や設定パネルなど使用頻度の低いアイコンは、必要な場所だけで読む2枚目に分けてください。技術上、ブラウザーやモバイル端末にはデコード画像寸法とキャンバスメモリの上限があり、巨大シートは低メモリのスマートフォンで警告なく縮小されたりデコードに失敗したりします。各辺が約2000ピクセルを超えるなら、それだけでも分ける価値があります。2xシートはすでに設計寸法の2倍であることも忘れないでください。

CSSでスプライトアイコンの色を変えられますか?

本当の意味ではできません。便利な答えではなく正直な答えです。色はラスターのファイルに固定されています。次で近似はできます filter — 連鎖 invert, sepia, saturate、そして hue-rotate 黒アイコンを目標の色相へ近づけます。ただし文字どおりの場当たり的な方法です。値は試行や計算で見つけ、正確なブランド色にはならず、多色の絵では壊れ、次の担当者には読めません。別色の2枚目のシートの方がフィルター連鎖より素直です。再着色が本当の要件なら、それはSVGを使うべきということです。

アイコンをスプライトシートへ配置

Sprite Genはドロップした画像を指定した余白で1枚のシートに詰め、座標を書き出します。キャンバスからオフセットを手で読み取る必要はありません。CSSを選ぶと、共通の基本ルールに次のものが含まれます: background-image, background-repeat: no-repeat および display: inline-block、さらに各スプライトの負の値を指定するルールを1つずつ追加します background-position ピクセル幅・高さも含み、上記の構造そのものです。SCSSを選ぶと同じ出力が次として得られます: %sprite-base プレースホルダーを、各アイコンのルールが次から取り込みます: @extend。JSONを選ぶと、独自のツールに渡すためのフレーム座標の生データが得られます。処理はすべてブラウザー内で行われ、どこにもアップロードされません。

スプライトシート生成を開く