Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

保存版: Web画像フォーマットを「正しく」扱う(2)SVGのマジック(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。画像はすべて元記事からの引用です。記事が長いので4分割し、章立てを浅くしました。記事の表示が重くならないよう、原文と同様に画像にloading="lazy"を指定しています。

  1. ピクセルとDPRを完全理解する
  2. SVGのマジック(本記事
  3. Evil Martians流巨大画像圧縮クックブック
  4. Webのアニメーション

保存版: Web画像フォーマットを「正しく」扱う(2)SVGのマジック(翻訳)

拡大縮小に強いSVG

SVGは"Scalable" Vector Graphicsの略なので、その定義からもSVG画像のスケーリングでは心配無用であることがわかります。その理由がおわかりでしょうか?

SVGファイルの中身をちょっと覗いてみましょう。最初に気づくのは、人間が読める平文テキストでコンテンツが書かれていて、昔なつかしのXMLと似ていることです。

以下は、私がシンプルな幾何学図形を用いてどうにかクッキーらしき絵を描き、それを.svgファイルにエクスポートしたものです。

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="120" viewBox="0 0 200 120">
    <g fill="none">
        <ellipse cx="100" cy="60" fill="#FDC970" rx="100" ry="60"/>
        <path fill="#794545"
              d="M69.5433628,33.5480878 C65.8626549,33.5480878 61.7766372,31.1161129 58.1720354,32.2999373 C56.2746903,32.9232602 56.3815929,38.2702194 60.5900885,38.6347335 C65.4106195,39.0530408 69.8828319,39.0225705 69.3709735,33.1215047 C69.3550443,32.9383072 69.0442478,32.9992476 68.881062,32.9383072 M122.896283,19.855674 C118.705133,19.4005016 114.333451,17.1182445 110.322832,18.4897806 C108.631504,19.0679624 108.557522,23.9431975 110.322832,24.1549843 L110.835398,24.7019436 C113.283894,24.9953605 115.762124,25.3203762 118.217699,25.1021944 C121.980531,24.7681505 123.187611,20.541442 119.776283,18.861442 M153.981593,50.2878997 C152.356106,49.1322884 150.801062,47.8563009 149.105841,46.8206897 C143.614159,43.4659561 139.981239,46.0728527 142.624779,53.3021944 C142.882478,54.0071473 143.838938,54.2219436 144.547965,54.2862696 C146.88177,54.499185 149.286726,54.6590596 151.573097,54.1188715 C152.198584,53.9710345 152.378407,51.1380564 152.378407,48.2467712 M90.3642478,74.3676489 C90.1281416,74.2999373 82.6669027,71.6516614 80.7334513,72.3396865 C79.4789381,72.7862069 76.300177,74.2623197 77.5178761,74.8126646 L77.1681416,78.876489 C84.1083186,82.0122884 88.0523894,79.6845141 87.441062,74.061442 M41.1472566,60.9460815 C34.3621239,61.3455799 42.7111504,61.193605 35.5578761,60.1429467 C32.7461947,59.729906 25.1047788,62.6068966 30.1058407,66.3366771 C36.7384071,71.2830094 41.4778761,66.7895925 40.3543363,60.5902194 M141.784425,93.0744828 C135.735221,93.0744828 143.533451,93.3603762 131.213451,89.6674608 C127.678938,88.6081505 125.312566,93.9363009 127.677168,96.1651411 C130.754336,99.0647022 140.590442,98.4154232 140.590442,93.0221944 M113.093097,50.5941066 C110.309381,50.4473981 107.51823,49.9011912 104.740885,50.1547335 C103.281416,50.2878997 102.962832,56.4951724 105.088142,56.8611912 C108.850973,57.5097179 119.12708,58.2022571 112.47292,49.9373041"/>
    </g>
</svg>

ここでは「まだ」クッキーの絵は見られません(しばらくお待ちください)が、上にはいくつかのタグがあることがわかります。

  • <svg>はこの画像のコンテナです。
  • <g>は構成の単位です(このタグはデモ用に置いたものなので、実際には上のコード例では無意味です)。
  • <ellipse><path>は基本的な幾何学図形です。<path>は最も自由な図形で、これを用いてどんな形でも描けます(円でも三角形でももっと複雑な曲線でも)。

<path>タグについて完全に理解したい方はこのチュートリアルをご覧ください。

この図形の外見は、属性(attribute)で決定されます。

この例では、rxdcxなどの属性がその絵のサイズや位置を表します。これらの属性の値は単なる座標ですが、画面上の座標系とはまったく無関係です。その代わり、値はSVG画像のviewBoxと相対的に算出されます。この例では以下のようになります。

viewBox="0 0 200 120"

上のviewBoxは、幅200ピクセル・高さ120ピクセルのドキュメントを、左から0ピクセル・上から0ピクセルの位置に変換します。このサイズは、画像が配置されるviewportのサイズと位置です。このSVG画像にはwidth属性とheight属性もあり、画像自体の実際のサイズを決定します。ここでは200 × 120 CSS pixelになります(①)。

SVG属性で遊んでみる

さまざまなSVG属性で遊ぶ: ①元の絵、②widthheightを削除、③width=90px;、④preserveAspectRatio: none;、⑤preserveAspectRatio: meet;、⑥preserveAspectRatio: slice

width属性とheight属性を削除してみましょう。こうすると、画像のサイズはブラウザウィンドウのサイズに応じて決定されるようになります(②)。

画像のサイズを設定するもうひとつの方法は、たとえば以下のようにCSSでwidthheightを設定することです。

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" style="width: 90px" >

こうすると、画像のサイズはブラウザウィンドウの高さの20%になります(③)。

ちょっと待った、クッキー画像が上下の中央に配置されている理由は何でしょう?実は、preserveAspectRatioと呼ばれるもうひとつの属性があるのです。

この属性のデフォルト値は xMidYMid meetで、「画像の元の縦横比を維持し、コンテンツの現在のviewBoxをvisibleにし、画像を可能な限りスケールし、水平にも垂直にも中央配置する」という意味です(この名前から意味を推測するのは無理でしょうね)。

今度は上の図の④と⑥の属性で遊んでみましょう。

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" preserveAspectRatio="none" style="width=90px">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" preserveAspectRatio="xMinYMin meet" style="width=90px">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" preserveAspectRatio="xMinYMin slice" style="width=90px">

クッキー画像を半分だけ表示したいときはどうすればいいでしょう?viewBoxのwidthを減らすだけでできます。

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 120" preserveAspectRatio="xMinYMin meet" style="width: 90px">
SVGのviewBoxを変更する

SVGのviewBoxを変更する

viewBoxpreserveAspectRatioは最高の友だちです。

これらの属性を使いこなせば、画像を数百万通りの方法でスケールできるようになります。

SVGに秘められたパワー

SVGには、「タグ」「曲線」「座標」以外にもいくつかの秘密兵器が隠されています。1つ目は、SVG画像の内側に「ラスタ画像」を配置できる機能です(これに該当しない場合は、常に今自分が扱っているSVG画像が「本物の」ベクタ画像であることを常に確認してください)。この手法は、ラスタ画像に何らかのインタラクティブ性を追加したい場合や、ラスタ画像を何らかの方法でマークアップしたい場合に便利です。

2つ目は、SVG画像内のパスをアニメーションする機能です。この機能は、SVGアニメーションの美しい世界へといざなう扉を開いてくれます。

最後にご注意申し上げますが、SVGファイルは「危険」にもなりえます。SVGのXML構文の中に誰かが<script>タグや何らかのJavaScriptコードを押し込むことについては防げません。ブラウザはこのコードを嬉しそうに実行することでしょう。

AnyCable Webサイトのアニメーションをぜひご覧ください。一部はSVGアニメーションツールで動いています!

SVG内部に仕込まれたscriptタグ

SVGファイルは危険にもなりうる

信頼できない場所から取得したSVG画像を扱うときは、常に細心の注意を払ってください。一般に、WebでSVG画像を公開するときは常に内部を詳しく調べるのがよい方法です。

SVGフォーマット vs ラスタフォーマット

なるほど、SVG画像がすごいパワーを秘めていることはわかりました。ではシンプルな画像でも常にSVG画像を使うべきなのでしょうか?いいえ!以下のクッキー画像はSVGでは26kBですが、後述するWebPなら同じ数のクッキー画像をわずか16kBで保存できます。しかも両者の見た目に何の違いもないとしたら、わざわざSVGにする価値はあるでしょうか?

たくさんのクッキー画像(SVG) たくさんのクッキー画像(WebP)
クッキーのSVG画像 クッキーのWebP画像

SVGはブラウザのコンソールで作成や編集ができますが、その作業をより快適かつプロ仕様のセッティングで行うためのベクタ画像エディタを選べます。私たちはBoxy SVGをおすすめします。このエディタにはベクタグラフィックスをGUIの中で扱うオプションがフルセットで揃っていますし、コードをいじりたい人向けにコードエディタも内蔵されています。

SVG画像をReactコンポーネントに変換してsvgoで最適化したい方は、svgrをご覧ください。

いよいよこのSVG画像をsvgoで最適化するときがやってまいりました。最終的に13kBのSVG画像と16kBのWebP画像が得られました。またしてもベクタの方がサイズが小さくなりましたね!
今度はアセットの圧縮を正しくセットアップしたいと思います。SVG画像は単なるテキストファイルなので、圧縮はとてもよく効きます。

しつこいですが「完璧なフォーマットは存在しません」

しかし現在のタスクに適した正しいフォーマットを選んで存分に最適化をかけることはもちろん可能です。

それでは再びラスタグラフィックスに話を戻しましょう。

そもそも「画像圧縮」とは?

簡単な算数をやってみましょう。1000x1000の画像がひとつあるとすると、ひとつひとつのピクセルは4種類の数値「3つの色情報と1つの不透明度(opacity)」で表現できます。それぞれの数値は1バイト(8ビット)で表現できるので、生の画像データは4 * 1000 * 1000 * 1バイトすなわち4MBというかなり大きなサイズになります。送信側はこの画像を圧縮してからブラウザに送信し、ブラウザは圧縮画像をデコードして元に戻す必要があります。

圧縮アルゴリズムは「可逆圧縮(lossless: ロスレスまたは劣化なしとも)」「非可逆圧縮(lossy: 劣化ありとも)」に大別されます。前者は、画像を構成するピクセルがデコード後に正確に元通りになることを保証しますが、後者はサイズを節約するために画像の画質を落とすことをためらいません。

ロスレス圧縮の限界については、有名な「シャノンの情報源符号化定理」をご覧ください。

圧縮を担当するのは「エンコーダー」というプログラムで、元の画像に対して圧縮のさまざまな処理段階で動作するアルゴリズムのパイプラインを利用します。処理段階段階によっては元のデータが失われて劣化圧縮となることもあります(なお、上述した画像の縦横サイズの縮小(ダウンスケール)も劣化の一種です)。

デジタル成果物を扱う場合、圧縮アルゴリズムを背後で支える数学を必ずしも理解しなければならないわけではありません。知っておくべきは「エンコーダーの選択」「エンコーダーの設定」「何種類の画像画質を得たいか」だけであり、容認できる結果は完全に「その画像が利用されるコンテキスト」次第で変わります。

完全な圧縮方法というものは存在しません。そのユースケースに適した圧縮方法が存在するだけです。

エンコーダーの違いを実際に見てみましょう。

Sketchからエクスポートしたカップケーキ画像 OptiPNGでエクスポートしたカップケーキ画像は左より21%小さい
Sketchでエクスポートしたカップケーキ画像 OptiPNGでエクスポートしたカップケーキ画像

  1. ピクセルとDPRを完全理解する
  2. SVGのマジック(本記事
  3. Evil Martians流巨大画像圧縮クックブック
  4. Webのアニメーション

関連記事

保存版: Web画像フォーマットを「正しく」扱う(1)ピクセルとDPRを完全理解する(翻訳)

保存版: Web画像フォーマットを「正しく」扱う(3)Evil Martians流巨大画像圧縮クックブック(翻訳)

保存版: Web画像フォーマットを「正しく」扱う(4)Webのアニメーション(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。