本文へスキップ

CSSスプライト作成 · くっきりしたスプライトシートとクラス

Webゲームやサイト向け:何十もの画像リクエストを1つに。フレームごとに背景位置とサイズを持つCSSクラスになり、プレビューページですべてをくっきり確認できます。

CSSスプライトは、多数のアイコンやフレームをまとめた1枚の画像です。要素を1つの断片とちょうど同じ大きさにし、background-positionを断片の位置のマイナスにしてシートをずらすと、その断片だけが見えます。Nerulioは画像を1枚のPNGにパックし、画像ごとに1つのクラスを持つ.cssとプレビュー用の.htmlを書き出します。要素の枠ではトリムのオフセットを戻せず、回転した断片も戻せないので、トリムと回転はオフにします。image-rendering: pixelatedで拡大してもドット絵がくっきりします。全クラスをChromiumで描いて比較しました。

パックするフレーム画像、GIF、.asepriteファイルをドロップ
空のStudioを開く
ブラウザ内で処理し、ファイルはアップロードされません。
Studio のパック&書き出し:各フレームの枠が表示されたアトラスページ、使用率、書き出し先
CC0アセット(OpenGameArt)を開いた実際のStudio画面です。
書き出し先
  • CSS sprites検証済み· Chromium
  • Godot 4検証済み· Godot 4.7.2
  • Unity 6検証済み· Unity 6000.5.3f1
  • Phaser 3 / 4検証済み· Phaser 3.90 + 4.2
  • PixiJS 8検証済み· PixiJS 8.21
  • Aseprite JSON検証済み· Phaser load.aseprite · PixiJS 8.21
  • .aseprite検証済み· Aseprite 1.3.18
  • Spine / libGDX検証済み· spine-canvas 4.2
  • Starling / Sparrow XML検証済み· Phaser 4.2
  • LÖVE検証済み· LÖVE 11.5
  • Defoldビルド確認· Defold bob.jar 1.13.1
  • GameMaker未検証
  • GIF / APNGデコード確認· Pillow

CSSスプライトが1つの断片を見せる仕組み

要素は1つの断片とちょうど同じ大きさで、シートはその背景です。断片が枠の中に来るように、シートを左上へずらします。x = 42、y = 31にある断片ならbackground-position: -42px -31pxです。シートの残りは枠の外にあり、描かれません。

枠が見える範囲のすべてなので、トリムした断片は透明な余白を失ってずれ、回転した断片は背景のプロパティだけでは戻せません。そのためCSSプリセットは元の大きさの正立した画像をパックし、拡大しても隣に届かないよう2pxの間隔を空けます。

大きく描くには、すべての数値(幅、高さ、background-size、background-position)を同じ倍率で掛けるか、プレビューページのようにzoom: 4で要素ごと拡大します。image-rendering: pixelatedは、滑らかに補間せず最近傍で整数倍に拡大するようブラウザに伝えます。

background-position
要素内でシートをずらす量。断片の位置のマイナス。
background-size
CSSピクセルでのシートの大きさ。ページごとに書かれているので拡大の結果が予測できる。
image-rendering: pixelated
ドット絵向けの最近傍拡大。背景画像にも効く。

例:40 × 29のフレーム6枚をCSSクラスに

run.png 82 × 91(40 × 29のフレーム6枚、トリムなし、間隔2)

.sprite{display:inline-block;background-repeat:no-repeat;image-rendering:pixelated;}
.sprite-page-0{background-image:url("run.png");background-size:82px 91px;}
.sprite-run_3{width:40px;height:29px;background-position:-42px -31px;}

HTML element: class="sprite sprite-page-0 sprite-run_3"

zoomなしで同じフレームを2倍に:
  width 80px   height 58px   background-size 164px 182px   background-position -84px -62px

run_3は左から42px(run_2の40px+間隔2px)、上から31px(1行目の29px+間隔2px)の位置にあるので、そのマイナスがオフセットになります。

できること

  • スプライトシートPNGと、フレームごとに1クラスの.css。
  • 同梱スタイルシートで全クラスを描くプレビュー.html。
  • どの倍率でもドット絵をくっきり保つimage-rendering: pixelated。
  • CSSではトリムと回転をオフ(要素ボックスはトリムのオフセットを持てないため)。Chromiumでクラスごとに確認。

使い方

  1. 画像やフレームをドロップ。
  2. パック&書き出しでCSS spritesを選ぶ。
  3. プレビューページで全クラスを確認。
  4. .cssを読み込み、要素にクラスを付ける。

Webページで使う

  1. run.pngとrun.cssをサイトにコピーし、ページのheadでstylesheetのlink要素からrun.cssを読み込みます。
  2. 要素ごとにクラスを3つ付けます。sprite、sprite-page-0のようなページのクラス、sprite-run_3のような画像のクラスです。
  3. フレームアニメーションは、JavaScriptのタイマーでフレームごとに画像のクラスを切り替えます。
  4. スプライトがボタンのアイコンなら、ボタンにアクセシブルなラベルを付けてください。スプライトの要素自体には文字がありません。

結果の確認

  • 書き出したrun.htmlを開きます。全クラスが4倍で、下に名前付きで描かれます。
  • ブラウザを拡大します。image-rendering: pixelatedなら縁は四角いままです。縁に隣の断片が少し見えるなら、位置を変えずにbackground-sizeだけを変えたか、間隔を0にしています。

トラブルシューティング

症状考えられる原因確認方法対処
断片に隣の断片の一部が見える要素が断片より大きい、または位置を変えずにbackground-sizeだけ変えた要素の幅と高さをクラスと比べるすべての数値を同じ倍率で変えるか、zoomやtransformで要素を拡大
ドット絵がぼやける要素にspriteクラスがなく、image-renderingが効いていない開発者ツールで計算済みのimage-renderingを見るspriteクラスを付ける
何も表示されない画像のパスが違う:url("run.png")はCSSファイルからの相対パスネットワークパネルでrun.pngが404PNGをCSSの隣に置くか、urlを直す
フレームに別のフレームの断片が見える要素がsprite-page-0なのに、そのフレームはページ1にあるプレビューのHTMLでそのフレームに使われているページのクラスプレビューがそのフレームに付けているページのクラスを使う

ほかの方法

方法こんなときに向いています
個別のPNGファイル、アイコンならSVG頻繁に変わる数枚の画像は個別のファイルのほうが更新しやすく、ベクターのアイコンはPNGスプライトではなくSVGが向いています。
ゲームエンジンのアトラスcanvasやWebGLのゲームなら、JSONアトラスを読み込みます:Phaser、PixiJS。
先に絵を拡大するCSSの拡大なしで決まった大きなサイズで見せたいなら、パックの前に整数倍で拡大します。

エンジン別の書き出しと検証方法

「検証済み」は、実際のCC0アセットから書き出したファイルを本物のエンジン・ツールで読み込み、元データと比較したという意味です。読み込んでいない形式はそのように表示します。

書き出し先ファイル検証
Godot 4.tres SpriteFrames · .tscn AnimatedSprite2D · .png.import検証済み Godot 4.7.2Godotで読み込み、全アニメーションフレームを描画して元ファイルと比較。シーンはNearestフィルターで、回転は使いません。
Unity 6.unity.json · Editor/NerulioSpriteImporter.cs · AnimationClip検証済み Unity 6000.5.3f1バッチモードでスプライト範囲・ピボット・ピクセル・クリップのキーと長さを確認。
Phaser 3 / 4atlas JSON (multiatlas) · .anims.json検証済み Phaser 3.90 + 4.2両バージョンでフレームとアニメーションを描画。
PixiJS 8spritesheet JSON · animations · anchor · meta.scale検証済み PixiJS 8.21回転・複数ページのアトラスも確認。
Aseprite JSONhash / array · frameTags · slices検証済み Phaser load.aseprite · PixiJS 8.21タグの方向と繰り返し回数を保持。
.asepritelayers · tags · durations · slices検証済み Aseprite 1.3.18Asepriteのコマンドラインで開いて描画。
Spine / libGDX.atlas検証済み spine-canvas 4.2Spineランタイムで回転・トリム領域をすべて描画。libGDX本体は未実行。
Starling / Sparrow XMLXML per page検証済み Phaser 4.2Phaser 3.90はトリムされたXMLフレームを誤って描画します(Phaser側の不具合)。「Sparrow XML (Phaser 3)」プリセットはトリムを切り、3.90・4.2とも合格。
LÖVELua quads · nerulio_atlas.lua · main.lua検証済み LÖVE 11.5同梱ヘルパーで全フレームを描画し、全アニメーションを再生。
Defold.atlas · .tilesourceビルド確認 Defold bob.jar 1.13.1Defoldのコマンドラインビルダーでビルドし、生成テクスチャのUVを読み戻して確認。
GameMakerspr_<name>_stripN.png · gamemaker.json未検証GameMakerでは読み込んでいません(ヘッドレス実行不可)。ストリップはGameMakerの取り込み方式どおりに切ります。
CSS sprites.css · preview .html検証済み Chromium同梱スタイルシートで全クラスを描画。
GIF / APNGone file per animationデコード確認 Pillow書き出し後に全フレームと遅延を再デコード。APNGは完全一致。

検証の根拠

書き出し:実在のCC0アセットで実際のエンジンを49回実行し47回合格。失敗2回はPhaser 3.90のトリムXMLの不具合で、Phaser 3プリセットで回避。実在の5フレームセットでパッカー比較(CodeAndWeb無料Webパッカー、GAPTools、free-tex-packer):回転ありで5つすべて最小、回転なしで5つ中4つ。全フレームがピクセル単位で復元できます。

制限

  • ポリゴン(メッシュ)パッキングはありません。検証したエンジンのどれも、書き出しデータからメッシュアトラスを描画しないためです。
  • GameMakerのストリップはGameMaker本体では未検証です。
  • 拡大縮小バリエーション(@0.5x〜@4x)はニアレストネイバーのみです。
  • トリムしたStarling XMLはPhaser 3.90で正しく描画されないため、「Sparrow XML (Phaser 3)」プリセットを使ってください。

バージョンと出典

Chromium(Playwright)で書き出したスタイルシートをそのままリンクし、.sprite-…のルールごとに要素を1つ描いて、スクリーンショットを元画像と比較しました。ほかのブラウザはこの実行に含まれていません。image-renderingとbackground-positionの動作はMDNの説明に従います。

参照した公式ドキュメント

よくある質問

トリムがオフなのはなぜ?

CSSの要素ボックスはトリムのオフセットを持てず、トリムしたフレームがずれます。そのためCSSは常にフレーム全体を保ちます。

どのブラウザで確認しましたか?

同梱スタイルシートでChromiumで全クラスを描画し、スクリーンショットと比較しました。

無料ですか?アカウントは必要ですか?

このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。

スプライトはアップロードされますか?

いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。

Asepriteやエンジンのプラグインは必要ですか?

いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。