31
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

amp-img から学ぶ画像の表示のベストプラクティス

Last updated at Posted at 2020-09-25

AMP は Google が推奨しているウェブコンポーネントフレームワークで、その実装には Web サイトのパフォーマンスを向上させるための知見が詰まっています。
AMP コンポーネントの実装を詳しく見ていくと、 AMP を導入せずにサイトを作る際にも役に立つベストプラクティスを学べるのではないかと思います。
以下では amp-img コンポーネントに注目して web サイト開発における画像表示の実装について掘り下げます。

amp-img

amp-img は AMP 対応のサイトで画像を表示する際に、 HTML の img タグの代わりに使用します。
AMP の built-in 要素であるため、 amp-img 専用の js を追加で読む必要はなく、AMP のランタイムを通して自動的に使用できます。

amp-img の基本の使い方

<amp-img src="/static/sample.jpg" width="1080" height="720" layout="fixed" alt="sample"></amp-img>

参考

画面の解像度に合わせた画像を表示

HTML の img タグと同様に amp-img タグでもsrcset 属性や size属性を使用することができます。
高解像度の画面(Retina ディスプレイなど)で高解像な画像を配信する場合きれいな画像を表示できるメリットがありますが、低解像度の画面でも必要以上に重い画像を時間をかけて読み込まなくてはなりません。
srcset 属性や size 属性を使用することで、画面の解像度に合わせて適切な画像を表示することができます。

<amp-img alt="sample"
  src="/static/sample-640.jpg"
  width="640"
  height="400"
  srcset="/static/sample-640.jpg 640w,
          /static/sample-320.jpg 320w"
  sizes="(min-width: 650px) 50vw, 100vw">
</amp-img>

アートディレクション

ブレークポイントの条件に合わせて画像を変更することをアートディレクションと言います。
amp-img では media 属性によってブレークポイントを指定して画像自体を切り替えることができます。
srcset属性 と size 属性による画像の切り替えは、基本的に解像度のみ異なる同一の画像を切り替えることが想定されていますが、media 属性の場合は画像のアスペクト比が異なっていても問題ありません。

<amp-img alt="sample"
  media="(max-width: 768px)"
  width="226"
  height="340"
  src="/static/sample-medium.jpg"></amp-img>
<amp-img alt="sample"
  media="(mim-width: 769px)"
  width="450"
  height="340"
  src="/static/sample-small.jpg"></amp-img>

通常の HTML タグでアートディレクションを実現する場合、picture タグを使用します。1

<picture>
  <source srcset="/static/sample-medium.jpg" media="(max-width: 768px)">
  <source srcset="/static/sample-small.jpg" media="(mim-width: 769px)">
  <img src="/static/sample-small.jpg" alt="sample">
</picture>

参考

遅延ロード

画像の遅延ロードは、画面に表示されていない画像は読み込まずに、画面のスクロールに応じてあとから画像を読み込むことでウェブページの表示を高速化する手法です。
通常の img タグを使用した場合、ページアクセス時にすべての img タグの画像を読み込みます。
これによりページが最低限の操作を受け付けるようになるまでに時間がかかってしまったり、スクロールしない場合に不要な通信が発生したり、といったことが起きます。

amp-img は画面のスクロール位置に応じて、amp-img タグ内に img タグを生成します。この時に初めて画像の取得のための通信が発生するため効率的に画像をロードすることができます。

<amp-img src="/static/sample.jpg" width="1080" height="720" layout="fixed" alt="sample"></amp-img>

<amp-img src="/static/sample.jpg" width="1080" height="720" layout="fixed" alt="sample" class="i-amphtml-element i-amphtml-layout-fixed i-amphtml-layout-size-defined i-amphtml-layout" i-amphtml-layout="fixed" style="width: 1080px; height: 610px;">
  <img decoding="async" alt="sample" src="/static/sample.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>

amp-img を使用せずに遅延ロードを行う場合、スクラッチで実装するほか、lazyloadなどのライブラリの利用や、img タグの loading 属性を使用する方法があります。(ただし、Safari と IE11 は未対応

非同期で画像をデコード

展開されたコードの img タグを見ると、decoding="async"の指定があります。
画像データは通常ファイルサイズを小さくするためにエンコードされているため、ブラウザは画像を表示する際はデータをデコードする必要があります。
img タグに decoding 属性によってブラウザに同期/非同期のどちらでデコードするかのヒントを提供でき、全てのモダンブラウザに対応しています。1

説明
sync 画像を同期的にデコードする
async 画像を非同期でデコードする
auto 優先設定なし(デフォルト値)

amp-img コンポーネントは、img タグにdecoding="async"を付与することで、メインスレッドの処理をブロックせずに画像を非同期的にデコードさせています。

参考

画像のフォールバック

amp-img では、例えば webp の画像を使用する際に、webp 未対応のブラウザ用に jpg の画像を設定したい場合など、画像の読み込みに失敗した際のフォールバック画像を指定することができます。

<amp-img alt="sample" width="1080" height="720" src="/static/sample.webp">
  <amp-img alt="sample" fallback width="1080" height="720" src="/static/samples.jpg"></amp-img>
</amp-img>

amp-img を使用しない場合、picture タグを使用することで実現できます。

<picture>
  <source type="image/webp" srcset="/static/sample.webp" /> // wepb 対応の場合は source タグの画像を提供
  <img src="/static/samples.jpg" width="1080" height="720" alt="sample" />  // wepb 未対応ブラウザで source タグの画像を提供できない場合の代替画像
</picture>

参考

レイアウトシフト対策

Google の提唱する UX 指標である Core Web Vitals の一つに Cumulative Layout Shift(CLS) があります。
これは予期せぬレイアウトのズレや崩れを独自に指標化し評価しているもので、画像が読み込まれた際のこのレイアウトの移動によってスコアが下がってしまいます。
amp-img コンポーネントは、画面幅によって画像の幅 / 高さが変化しない場合、画像を表示する明示的なサイズを widthheight に指定することで amp-img のインラインスタイルとして展開され、レイアウト時に画像の表示領域が確保されます。2

通常の img タグの場合もwidth / height属性への値を指定や、CSS でwidth / heightに具体値を指定することで画像を読み込む前に場所を確保し、レイアウトシフトを防ぐことができます。
2019 年 10 月に WHATWG が img タグの widht / height 属性に基づいてデフォルトのアスペクト比を設定できる仕様が標準化されました。
そのため現在のモダンブラウザでは、画面の幅によって画像の表示サイズが変化するレスポンシブな表示でも widthheight 利用してレイアウトシフトを防ぐことができます。

例えば、下記のようにな img タグを設置した場合、3 : 2 のアスペクト比でブラウザが表示場所を計算して確保してくれます。

<img src="/static/samples.jpg" width="1080" height="720" alt="sample">
img {
  width: 100%;
  height: auto;
}

参考

レスポンシブ対応のスタイル

layout 属性

amp-img コンポーネントは layout 属性を指定することにより、AMP のレイアウトシステムを利用できます。
layout 属性は AMP コンポーネント共通の属性ですが、コンポーネントによってサポートされる値が異なります。
amp-img コンポーネントは container 以外の下記のレイアウトに対応しています。

  • fill
  • fixed
  • fixed-height
  • responsive
  • flex-item
  • intrinsic
  • nodisplay

amp-img タグとその子要素の img タグには、layout 属性の指定に応じた class が指定されます。
レスポンシブ対応をする際に、ウインドウや親のコンテンツのサイズの変化に伴ってどのように画像の表示を変化させるのかは往々にして悩ましいものです。
そんな時に layout 属性による画像を様々な表示パターンとそのスタイルは参考になります。
下記ではそれぞれの layout の値を amp-img に指定した場合のスタイルを詳しく見ていきます。
なお、layout 属性は amp-img 専用の属性ではなく、AMP コンポーネント共通のものであるため、画像以外のコンテンツにも応用できます。
layout 値を指定した際のアニメーション画像は公式ドキュメントからお借りしました。

参考

layout="fill"

親要素のコンテンツボックス全体を埋めるように拡大縮小されます。
widthheight に指定したアスペクト比が親要素のアスペクト比と合わない場合は引き伸ばされて表示されます。
fill.gif

<amp-img src="/static/samples.jpg" width="1080" height="720" layout="fill" alt="sample"></amp-img>

↓ 画像ロード後

<amp-img src="/static/samples.jpg" layout="fill" alt="sample" class="i-amphtml-element i-amphtml-layout-fill i-amphtml-layout-size-defined i-amphtml-layout" i-amphtml-layout="fill" style="--loader-delay-offset:434ms !important;">
  <img decoding="async" alt="sample" src="/static/samples.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>
/* amp-img タグのスタイル */
.i-amphtml-layout-size-defined {
  overflow: hidden!important;
}
.i-amphtml-layout-fixed {
  display: block;
  overflow: hidden!important; /* .i-amphtml-layout-size-defined のスタイルによって打ち消される */
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}
/* img タグのスタイル */
.i-amphtml-layout-size-defined .i-amphtml-fill-content {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}
.i-amphtml-replaced-content {
  padding: 0!important;
  border: none!important;
}
.i-amphtml-replaced-content {
  padding: 0!important;
  border: none!important;
}
.i-amphtml-fill-content {
  display: block;
  height: 0;
  max-height: 100%;
  max-width: 100%;
  min-height: 100%;
  min-width: 100%;
  width: 0;
  margin: auto;
}

amp-img タグは .i-amphtml-layout-fixedに指定されているスタイルにより、position: absolute;であるため、直近のposition: static;以外の position の値を持つ親要素の上下左右いっぱいに広がるように表示されます。

img タグは .i-amphtml-layout-size-defined .i-amphtml-fill-content に指定されているスタイルにより、直近のposition: static;以外の position の値を持つ親要素、つまり amp-img の上下左右いっぱいに広がるように表示されます。
img が amp-img のサイズいっぱいになる構造は、どの layout を指定した場合でも共通しています。
layout="fill"の場合は amp-img タグと img タグが共にposition: absolute;であるため、基準となる親要素に高さを明示的に指定する必要があります。

.i-amphtml-replaced-contentに指定されているスタイルは、ユーザーエージェントスタイルを打ち消すためのスタイルです。
また、.i-amphtml-fill-contentに指定されているスタイルは、iOS で iframe をレスポンシブ対応させる際にコンテンツのサイズをコンテナーに収めるための指定のようです。(issue)
layout 属性は他のコンポーネントでも使用されるため、amp-img タグの場合でもこのようなスタイルが適応されます。
これらのスタイルは他の layout 値を指定した場合でも登場しますが、今回の内容には直接関係ないため以下では割愛します。

layout="fixed"

ウインドウのサイズが変化しても画像の大きさは変化せず、widthheight属性に基づいて固定の寸法のまま表示されます。

fixed.gif

<amp-img src="/static/samples.jpg" width="1080" height="720" layout="fixed" alt="sample"></amp-img>

↓ 画像ロード後

<amp-img src="/static/samples.jpg" width="1080" height="720" layout="fixed" alt="sample" class="i-amphtml-element i-amphtml-layout-fixed i-amphtml-layout-size-defined i-amphtml-layout" i-amphtml-layout="fixed" style="width: 1080px; height: 610px; --loader-delay-offset:226ms !important;">
  <img decoding="async" alt="sample" src="/static/samples.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>
/* amp-img タグのスタイル */
element.style {
  width: 1080px;
  height: 610px;
  --loader-delay-offset: 188ms !important;
}
.i-amphtml-layout-size-defined {
  overflow: hidden!important;
}
.i-amphtml-layout-fixed {
  display: inline-block;
  position: relative;
}

amp-img タグにはインラインスタイルとして widthheight属性で指定した値が指定されています。
overflow: hidden; が指定されているため、画像の表示サイズよりウインドウが小さくなった場合もスクロールは発生せずに画像は見切れて表示されます。

layout="fixed-height"

高さは固定のまま、幅は使用可能なスペースに合わせて広がります。

fixed-height.gif

<amp-img src="/static/samples.jpg" height="720" layout="fixed-height" alt="sample"></amp-img>

↓ 画像ロード後

<amp-img src="/static/samples.jpg" layout="fixed-height" height="720" alt="sample" class="i-amphtml-element i-amphtml-layout-fixed-height i-amphtml-layout-size-defined i-amphtml-layout" i-amphtml-layout="fixed-height" style="height: 720px; --loader-delay-offset:377ms !important;">
  <img decoding="async" alt="sample" src="/static/samples.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>
/* amp-img タグのスタイル */
element.style {
    height: 720px;
}
.i-amphtml-layout-size-defined {
  overflow: hidden!important;
}
.i-amphtml-layout-fixed-height {
  display: block;
  position: relative;
}

layout="responsive"

width / height 属性で指定されたアスペクト比を維持したまま、使用可能なスペースに合わせて拡大・縮小します。

responsive.gif

<amp-img src="/static/samples.jpg" width="1080" height="720" layout="responsive" alt="sample"></amp-img>

↓ 画像ロード後

<amp-img src="/static/samples.jpg" layout="fixed-height" height="720" alt="sample" class="i-amphtml-element i-amphtml-layout-fixed-height i-amphtml-layout-size-defined i-amphtml-layout" i-amphtml-layout="fixed-height" style="--loader-delay-offset:377ms !important;">
  <i-amphtml-sizer slot="i-amphtml-svc" style="padding-top: 66.6667%;"></i-amphtml-sizer>
  <img decoding="async" alt="sample" src="/static/samples.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>
/* amp-img タグのスタイル */
.i-amphtml-layout-size-defined {
    overflow: hidden!important;
}
/* i-amphtml-sizer タグのスタイル */
element.style {
    padding-top: 66.6667%;
}
.i-amphtml-sizer {
  display: block!important;
}  

layout="responsiveの場合、amp-img の直下に img タグに加えて i-amphtml-sizer タグが挿入されます。
amp-img タグの widthheight に指定した値から、画像のアスペクト比は 1080:720 = 3:2 となります。
このアスペクト比を維持した状態で、表示可能な領域内で画像を拡大・縮小させるために、2 / 3 * 100 ≒ 66.6667%;padding-top として与えています。
i-amphtml-sizer タグによって親要素の amp-img の大きさが確保され、その amp-img の大きさに合わせて img タグが広がることで、画像のアスペクト比を維持したまま画像が拡大・縮小されます。
この方法であれば、IE11 でもレイアウトシフトを防ぐことができます。

layout="flex-item"

フレックスボックスの親要素の直下でフレックスアイテムとして画像を扱いたい場合に使用します。
親要素のスタイルと兄弟要素の数によって画像の大きさが変化します。

flex-item.gif

<amp-img src="/static/samples.jpg" layout="flex-item" alt="sample"></amp-img>

↓ 画像ロード後

<amp-img src="/static/samples.jpg" layout="flex-item" class="i-amphtml-layout-flex-item i-amphtml-layout-size-defined i-amphtml-element i-amphtml-layout" i-amphtml-layout="flex-item" alt="sample">
  <img decoding="async" alt="sample" src="//static/samples.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>
/* amp-img タグのスタイル */
.i-amphtml-layout-flex-item {
  display: block;
  position: relative;
  flex: 1 1 auto;
}

flex: 1 1 auto; が指定されているため、フレックスボックスの直下に layout="flex-item"の amp-img を隣接して配置して横に並べた場合、同じ幅で並びます。
また、兄弟要素がlayout="flex-item"の要素のみで高さを持たない場合は、親要素のフレックスボックスに高さを明示的に与える必要があります。

layout="intrinsic"

intrinsic.gif

画像自体の本来のサイズか CSS による制限(max-width など)に達するまで、widthheight 属性で指定されたアスペクト比を維持したまま使用可能なスペースに合わせて拡大・縮小します。

<amp-img src="/static/samples.jpg" width="1080" height="720" layout="intrinsic" alt="sample"></amp-img>

↓ 画像ロード後

<amp-img src="/static/samples.jpg" width="1080" height="720" layout="intrinsic" alt="sample" class="i-amphtml-element i-amphtml-layout-intrinsic i-amphtml-layout-size-defined i-amphtml-layout" i-amphtml-layout="intrinsic" style="--loader-delay-offset:241ms !important;">
  <i-amphtml-sizer class="i-amphtml-sizer" slot="i-amphtml-svc">
    <img alt="" role="presentation" aria-hidden="true" class="i-amphtml-intrinsic-sizer" src="data:image/svg+xml;charset=utf-8,<svg height=&quot;720px&quot; width=&quot;1080px&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; version=&quot;1.1&quot;/>">
  </i-amphtml-sizer>
  <img decoding="async" alt="sample" src="/static/samples.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>
/* amp-img タグのスタイル */
.i-amphtml-layout-size-defined {
    overflow: hidden!important;
}
.i-amphtml-layout-intrinsic {
  display: inline-block;
  position: relative;
  max-width: 100%;
}
/* i-amphtml-sizer タグのスタイル */
.i-amphtml-layout-intrinsic .i-amphtml-sizer {
  max-width: 100%;
}
i-amphtml-sizer {
  display: block!important;
}
/* i-amphtml-sizer タグ直下の img タグのスタイル */
.i-amphtml-intrinsic-sizer {
  max-width: 100%;
  display: block!important;
}

layout="responsive"と同様に amp-img の直下に img タグに加えて i-amphtml-sizer タグが挿入されます。
しかし、layout="intrinsic"の場合は i-amphtml-sizer の直下にさらにもう一つ img タグが挿入されます。
layout="responsive"では、空の i-amphtml-sizer タグに指定された padding が画像の表示領域を確保していたのに対し、 layout="intrinsic"では i-amphtml-sizer タグの直下の img タグによって表示される透明な svg 画像によって画像の表示領域を確保します。
svg 画像のサイズは amp-img に指定した widthheight 属性によって決まり、img タグであるため画像自体の大きさより大きくなることはありません。

また、i-amphtml-sizer タグ内の img タグには、role="presentation"aria-hidden="true" を指定することで、見た目を変えるために使用されている重要な意味を持たない要素であることを明示してあり、アクセシビリティにも考慮されていることがわかります。

layout="nodisplay"

要素は非表示になり、スペースを占有しません。

<amp-img layout="nodisplay" src="/static/sample.jpg" width="1080" height="720" layout="nodisplay"></amp-img>

↓ 画像ロード後

<amp-img layout="nodisplay" src="/static/sample.jpg" width="1080" height="720" class="i-amphtml-layout-nodisplay i-amphtml-element" hidden="" i-amphtml-layout="nodisplay"></amp-img>
/* amp-img タグのスタイル */
[hidden] {
    display: none!important;
}

AMP コンポーネント共通の hidden 属性が付与され、display: none;が適用されます。

まとめ

amp-img を使用しない場合でもwidthheightdecoding といった属性を適切に指定するなど、パフォーマンス改善のために画像周りで工夫できることが多くあることがわかりました。
amp-img は遅延ロードやフォールバックといった機能を提供してくれるので、改めて便利だと感じました。
完全に AMP 対応したサイトではないとしても画像を便利に扱うために amp-img を使用するのも一つの選択肢だと思いました。
画像の扱いは web サイトのパフォーマンスを低下させる大きな要因になり得るので、先人の知恵を借りながらベストな実装を追いかけていきたいですね。

  1. IEは非対応: https://caniuse.com/mdn-html_elements_img_decoding
    decoding 属性に指定できる値は下記の通りです。 2

  2. layout の値により、width / height が必須かどうかは異なります。
    画面幅によって画像の表示サイズが変化する場合は、適切なスタイルを付与することによりレイアウトシフトを防いでいます(後述)。

31
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?