abox.tools

言語: 日本語

画像をCSSの中に入れるべきとき、そうでないとき

スタイルシートに書き込まれた画像は、それと一緒に届きます。2度目のリクエストも、待ち時間もありません。その代わり、それはファイルであることをやめます。単独ではキャッシュされず、周りの何かが変わるたびに再ダウンロードされます。ここでは、その取引が割に合う場所と、静かに割に合わなくなる場所を説明します。

画像→データURI変換ツールを開く 画像まるごとを1行のテキストに。そのままCSSやHTMLに貼れます。

Markdownで表示

短い答え

画像をデータURIにを開いて画像を入れ、CSSカスタムプロパティを選び、その行をスタイルシートの先頭に貼ります。あとは必要なところでbackground-image: var(--logo)として使ってください。

そうするのは、画像が小さく、しかもどのページでも必要な場合です。アイコン、箇条書きの点、山形記号、模様といったものです。写真ではやらないでください。以下は、この2文がなぜ違うのか、そして自分がどちらを見ているのかを見分ける方法です。

データURIとは実際何なのか

あるものを指すのではなく、そのものを含んでいるアドレスです。スタイルシートは、ふつうこう書きます。

background-image: url("logo.png");

そしてブラウザはlogo.pngを取りに行きます。データURIは、こう書きます。

background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...");

そして、取りに行くものはありません。画像はすでにそこに、文字として書き出されています。部分は3つです。data:がスキーム。image/pngがメディアタイプで、ブラウザはこれを完全に信じます。それについては後述します。カンマから後ろがファイルです。

考え方は、これで全部です。小手先の技でも裏技でもありません。1998年から標準にあり、それ以降に出たすべてのブラウザで動きます。

得られるもの。往復が1回減る

節約されるのは帯域ではありません。リクエストです。

ブラウザは、logo.pngに言及しているスタイルシートを読むまでは、それを要求できません。そしてそのスタイルシートを取ってくるまでは、読めません。ですからふつうの背景画像は、ページ読み込みの中で少なくとも往復2回分の深さにあります。そして遅い回線のスマホでは、ファイルがどれだけ小さかろうと、1回の往復が数百ミリ秒になりえます。600バイトの山形記号は、転送にほとんど費用がかからないのに、届くまでに4分の1秒かかることがあるのです。

埋め込めば、スタイルシートと一緒に届きます。利点はそれで全部ですし、画面の上部に現れる小さなアイコンにとっては、それは本物の利点です。

代償。3分の1、そしてキャッシュ

base64は3分の1ほどを足します。ファイルの3バイトが4文字になります。URLが許す文字だけで任意のバイト列を書くには、それだけかかるからです。それを避ける賢いエンコーダーは存在しません。9KBのPNGは、12KBのスタイルシートです。

圧縮は、それを返してくれません。ここが、人が勝手に大丈夫だと思っている部分です。GzipやBrotliは冗長性を見つけることで働きますが、PNGもJPEGもWebPも、すでに圧縮されています。残っている冗長性はごくわずかですし、base64は何も足しません。実際に取り戻せるのは、その3分の1のさらに10分の1程度で、全部ではありません。(SVGは逆の場合で、次の節がそれについてです。)

ファイルであることをやめます。これは、たいていの測定には現れない代償であり、大きさが効いてくると問題になるほうです。

おおよその目安です。ブラウザの挙動が変わる境目ではなく、助言が変わる境目だと思ってください。2KB程度までなら明らかに得。10KB程度までなら、どのページにも出るものについてはたいてい見合います。50KBを超えると、エラーメッセージの出ない間違いです。この道具は、各結果がどの帯に落ちるのかを、文字数とともに表示します。

出力のカード。base64のデータURIを含むCSSの規則と、その横に元ファイルの大きさとエンコード後の大きさ。
エンコードした写しは、元のファイルより三割ほど大きくなります。この節が扱う代償はこれで、見つけさせるのではなく書き出してあります。

SVGをbase64にしてはいけません

埋め込み画像に関していちばんよくある間違いがこれで、書き出し機能やビルドプラグインが、人と同じくらいの頻度でこれをやります。

SVGはテキストです。URLはもともとテキストを運べます。逃がす必要のある文字はほんのひと握り、%、#、<、>、そして囲んだ引用符だけで、それ以外はまったくそのままにできます。その方式でエンコードすると、同じファイルのbase64よりだいたい5分の1短いURIができますし、しかもそのあと、ノイズではなくテキストとして圧縮されます。

そのうえ、読めるままです。

background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E...");

viewBoxが見えます。何もデコードせずに、エディター上で塗りの色を変えられます。同じファイルをbase64にすれば、それは二度と誰も触らない文字の壁になります。画像をデータURIには、SVGだと判明したものに対してこれを自動で行いますし、;base64をどうしても要求するツールチェーン向けに、チェックボックスも用意しています。

SVGのときだけ壊れる引用符の間違い

CSSはurl()を引用符なしで書くことを許していますし、ふつうのファイル名なら、それで問題ありません。

background-image: url(logo.png);

同じことをパーセントエンコードしたSVGでやると、壊れます。引用符のないurl()のトークンは、最初の空白、丸括弧、引用符、制御文字で終わります。そしてSVGは空白だらけです。属性と属性のあいだ、パスの数値と数値のあいだ、どこにでもあります。すると宣言は不正になり、CSSは不正な宣言を黙って捨てるので、背景も出ず、エラーも出ません。

直し方は、毎回引用符を付けることです。

background-image: url("data:image/svg+xml,%3Csvg ... %3E");

これは、エンコーダーが空白を逃がす必要のない理由でもあります。引用符で囲まれたURLの中では空白は完全に合法ですし、ひとつずつ%20にしていたら、ファイル中の空白1つにつき3文字かかります。この2つの判断は、対になっています。URIを引用符で囲めば、空白はそのままにできます。この道具が出すどの形も引用符で囲まれているのは、まさにこの理由です。

メディアタイプは正しくなければなりません

データURIは自分の種類を宣言し、ブラウザはその言葉をそのまま受け取ります。取得したファイルに対して行われるような、中身を見て推測する代替手段はありません。実際はJPEGのものについてimage/pngと言えば、画像は表示されませんし、役に立つ場所にメッセージも出ません。

これが効いてくるのは、拡張子が嘘をつくからです。JPEGとして書き出された写真がlogo.pngという名前になっているのは、ディスクの上でふつうに見つかるものです。一方、画像ファイルの先頭数バイトは、それが何であるかを曖昧さなく述べています。どの形式にも署名があるからです。ですから道具は、名前ではなくファイルを読むべきです。ここの道具はそうしますし、両者が食い違うときには知らせます。

混乱を招く形で失敗するので、知っておく価値のある形式が2つあります。iPhoneが写真を撮る形式であるHEICと、スキャナーが作るTIFFです。どちらも完全に正しいデータURIになりますが、Safari以外のブラウザは、それを描きません。URIが壊れているのではなく、その形式がウェブの対応するものではないだけです。先に変換してください。

公開するつもりのなかったメタデータ

データURIは、ファイルの1バイトずつの複製です。デコードも書き直しも行われませんし、たいていそれこそが目的です。画質が失われないからです。ですが同時に、ファイルの中のそれ以外もすべて付いてくる、ということでもあります。

スマホから出したばかりの写真は、EXIFを抱えています。撮影地のGPS座標、撮影日時、カメラの機種、そしてしばしばそのシリアル番号です。それがファイルの30KBになることもあります。埋め込めば、スタイルシートの中の40KBのbase64となり、すべてのページのクリティカルパスに乗ります。そして自宅の住所が、誰も見ようと思わない形でリポジトリにコミットされます。

先にEXIFビューアー・削除ツールで外してください。画像に触れずに、入れ物だけを書き直します。そのガイドもあります。画像をデータURIにする道具は、JPEG・PNG・WebPにどれだけメタデータがあるのかを読み取り、何かをコピーする前にそれを伝えます。

手に入れたら、どこに置くか

画像が1つのルールにしか出てこないなら、URIをそのルールに置いてください。2つ以上に出てくるなら、カスタムプロパティとして一度だけ宣言してください。アイコンはたいてい2つ以上に出てきます。ホバー時の状態とダークテーマを数えれば、なおさらです。

:root {
  --icon-search: url("data:image/svg+xml,%3Csvg ... %3E");
}

.search-field { background-image: var(--icon-search); }
.search-button::before { content: var(--icon-search); }

3KBのURIを4つのルールに貼れば、12KBのスタイルシートと、アイコンを変えるときに直す4か所になります。カスタムプロパティなら、どちらも1つです。テーマ切り替えが成り立つ形でもあります。メディアクエリの中で--icon-searchを定義し直せば、それを使っているすべてが従います。

CSSではなく<img>タグに入れる場合は、widthとheightを書いてください。埋め込まれた画像は即座に読み込まれるので、サイズがないと、見えないほど速く起きるレイアウトのずれが発生しますし、それでも減点はされます。例外はSVGです。viewBoxしか持たないSVGには固有のピクセルサイズがないので、ブラウザの既定である300×150をタグに書き込むと、拡大縮小できる画像を、誰も選んでいない大きさに固定してしまいます。

altは、そこに書くべき本当のことがない限り、空にしておいてください。その画像が意味を運んでいるのか、飾りなのかを知っているのは書く人だけですし、ファイル名から推測した説明は、スクリーンリーダーを使う人にとって、説明がまったくないより悪いからです。

形のカード。出力を選ぶボタンが並び、CSSの背景規則、imgタグ、URIそのもの、そしてbase64か素のSVGかのスイッチがあります。
どこへ持っていくかで出力が決まるので、コピー&ペーストの練習に任せず先に尋ねます。

答えが「やめておけ」のとき

エンコード後に50KBを超えるなら、埋め込みは間違った道具であり、エンコードをどれだけ丁寧にやっても直りません。試す価値のある順に、代わりの手を挙げます。

これにアップロードは要りません

ファイルをbase64にエンコードするのは、計算です。ブラウザが最初から持っている2つの関数、btoaとencodeURIComponentであり、書き方を変えるために画像がサーバーまで往復しなければならない技術的な理由は、一切ありません。これをするためにファイルをアップロードさせる変換サイトは、こちらの理由ではなく、自分の理由でアップロードしています。

ここの道具は、どこへも送りません。ページのContent-Security-Policyには接続してよいアドレスがすべて挙がっていますが、そのどれも当サイトのものではありません。言われるより確かめたい方は、ページを読み込んでから回線を抜き、それでもエンコードしてみてください。オンライン変換サイトにファイルをアップロードしても安全ですか?には、どの道具にも試せる確認方法がさらに3つ載っています。