チェックリスト
Checklist
1. 画像
画像を閲覧する利用者にとって、ほぼ同等品質の画像であればファイルサイズは小さい方がよいでしょう。特にモバイル回線では消費したデータ量で課金されることが多いので、より好ましいといえます。
また同様に、画像は早く表示された方がよいでしょう。現在の日本での回線速度は十分に高速化されているため体感できるほど最適化するのは難しいですが、ネットワークが弱い場所や通信量の予算超過時(ギガ切れ)、3Gなどの遅い環境で利用する場合には顕著でしょう。
さらに、サービスを運営する側にとっても利点があります。多くの配信サーバーでは取得数や転送量によって料金が決められています。その中でも特に画像が占める割合は多く、そのファイルサイズが小さくなると運営費用を減らせます。
NOTE: 画像容量を減らした場合には画質も必ずチェックしましょう。画質が荒くなりすぎると利用体験やコンバージョンに悪影響を及ぼす場合があります。
🚨 ✅ 🎨 1.1 適切な画像フォーマットを選択する
ディスプレイサイズ・密度が多様化していることから、拡大縮小に対応できるベクター形式(SVG、iOSはPDFの場合があります)で書き出せるか検討しましょう。作成時には、パス(ポイント)をできる限り減らしておきましょう。
パスが複雑な場合にはファイル容量が大きくなったり、CPU負荷が増したりすることがあるため、違う画像形式を検討します。
まずはWebP形式を検討します。ブラウザ対応のためにWebP形式を利用できない場合にはJPEGまたはPNG形式を検討します。なお、実装時に複数形式の画像を配信できる場合には、WebP、AVIFなどの比較的新しい形式を利用してもいいでしょう。
PNG形式を利用するときに、色数が少ないイラストなどの画像の場合はPNG-8を選択するとよいでしょう。後述するImageOptimではPNG形式まで選べないため、Squooshなどで色数を減らします。
多くのイラストは256色で十分なので、PNG形式の場合は積極的に色数の削減を試しましょう。
色数の多い画像にはJPEG形式を選定するとよいでしょう。PNG-24形式は画質が綺麗ですが、容量が大きくなりすぎるため、できる限りJPEG形式を選択します。イラスト的なものでもファイルサイズを減らす上では、PNG形式ではなくJPEG形式の方が適している場合があるので、実際に変換して比較しましょう。
例えば、Amebaスタッフブログのヘッダー画像の場合、PNG-24形式よりも、JPEG形式の方が軽量でした。JPEG画像の最適化にはMozJPEGを利用し、品質値80で書き出しました。(ただしこの場合は色数が少ないので、PNG-8形式が最適です)
✅ 🎨 1.2 アニメーション用途には動画形式を利用する
アニメーション用途には、できる限り動画ファイルを利用します。特にアニメーションGIFは、ファイルサイズが大きくなりやすいほか、CPUに負荷をかけがちなため、利用を推奨していません。
アニメーションに対応した画像ファイルを利用する場合には、フレーム数を少なくする、画像サイズを小さくするなど工夫してください。
🚨 ✅ 🎨 1.3 画像を最適化する
同じ形式の画像でも適切に圧縮することで見た目はほぼ変わらないまま、ファイルサイズを削減できます。まずImageOptimで最適化し、それでもファイルサイズが大きい場合は、Squooshなどを利用して画像形式や画像品質を調整します。Amebaではファイルサイズが40KBを超える場合にSquooshの利用を推奨しています。この値は暫定的な基準値であり、将来的に変更される可能性があります。
また、画像を配信する際には自動的に最適化してくれるサービス(Akamai Image Manager、Fastly Image Optimzer、imgixなど)を併用するとよいでしょう。ただし、低画質の画像を変換すると画質が悪化するので自動的に変換する場合は、オリジナルの画像をできる限り高品質にすると良いでしょう。理想的にはLossless画像(PNGやTIFF)から変換することが好ましいですが、変換がかからなかった場合やディスク容量、転送量などを踏まえて高画質なJPEGをオリジナルとしておくことも現実的にはありでしょう。
NOTE: Squooshで変換する場合、変換方法は「Browser*」でないものを選択してください。Browserの変換は最適化目的ではありません。また、PNG形式を変換する場合には、デフォルトの設定ではImageOptimと容量は変わりません。より減らしたい場合には、「Reduce palette」オプションを指定してください。ただし画質は劣化する場合がありますので必ず目視してください。
NOTE: 画像の品質を比較する際には、SSIMやMSE/PSNRなど機械的な方法で比較するのもよいでしょう。一般的によく使われているSSIMでは、0.999(暫定値)を下回る場合には再度調整を 検討しましょう。それらはImageMagick、FFmpeg、Image SSIMなどのツールで測定できます。ただし、自動的に比較しきれないパターンもあるので必ず目視もしましょう。
🚨 ✅ 1.4 表示に必要なサイズで画像をリクエストする
表示領域を大幅に超える画像を取得、表示することは、ネットワークの転送量、端末のメモリ・ストレージなどの無駄に繋がります。例えば、横幅200px、縦幅100pxで表示したい領域に対して横幅1200px・縦幅600pxの画像を配信するのは大きすぎます。
近年ではディスプレイピクセル比の向上により拡大した画像を用意する必要もありますが、例外を除きAmebaでは、最大でも2倍サイズを用意すればよいこととします。
ただし、拡大用途など利用者が明らかに高画質な画像を望んでいるシチュエーション、文字がぼやけてしまうと困る場合などでは端末のピクセル比に応じたサイズを用意してもよいでしょう。逆に、常に小さく表示されるサムネイル画像は1倍の画像のみを用意することも考えられます。
リストの画像は、小さく表示され高画質な画像が要求されないので、1倍かつ品質を落とします。
✅ 1.5 同一画像を複数サイズで配信する場合には、キャッシュ効率を考慮する
1.4で触れたように、端末できれいに画像を表示するためにはディスプレイピクセル比ごとに複数サイズの画像を用意するとよいですが、その場合には種類の作り過ぎに注意しましょう。
今では数え切れないほどの画面サイズがあり、それぞれに画像ファイルを用意しようとすると、?size=320
、?size=470
、?size=800
、?size=1024
などのように多くの画像URLができます。
これは、サーバーサイドとクライアントサイドでのキャッシュ効率が悪化し、結果的に表示速度が遅くなるためです。多くの場合、https://example.com/image.jpg?size=800
といったURL文字列を識別子としてキャッシュを再利用しています。そのため、サーバーサイドでは再度画像を生成しなければならなかったり、クライアンサイドでは端末上のキャッシュではなくネットワークを経由して画像を取得しなければなりません。
キャッシュ効率を向上させるためには、画像サイズの種類を限定することが有効です。例えば、大中小で3つくらいの種類を用意し、メイン画像・リストカード・サムネイルなどに用途をわけると良いでしょう。
✅ 1.6 すぐに読み込む必要のない画像は遅延リクエストする
すぐに読み込む必要のない画像は、スクロールに応じて要素が画面表示に近付くにつれて画像を取得します。そうするとネットワーク転送量を減らせ、さらに他の要素の表示が早くなる可能性もあります。
下記はWeb実装例です。
<!-- 遅延表示しない画像はloading="eager"を付与する -->
<img slt="" height="100" loading="eager" src="https://example.com/eager.jpg" width="300">
<!--
遅延表示する画像は以下の対応をする
1. srcをdata-srcにする
2. loading="lazy"を付与する
3. 壊れた画像のアイコンが表示されないようにsrcに透明な画像をセットする
4. JavaScriptが使えないブラウザ用に<noscript>を用意する (オプション)
-->
<img alt="" height="200" data-src="https://example.com/lazy.jpg" src="data:image/svg\\+xml;charset=utf-8,<svg title='Image Placeholder' xmlns='http://www.w3.org/2000/svg' role='presentation' viewBox='0 0 100 200' />" loading="lazy" width="100">
<noscript><img alt="" height="200" src="https://example.com/lazy.jpg" loading="lazy" width="100"></noscript>
<script>
(function() {
const lazyImages = document.querySelectorAll("img[loading=lazy]");
if ("loading" in HTMLImageElement.prototype) {
lazyImages.forEach(function (lazyImage) {
lazyImage.setAttribute('src', lazyImage.dataset.src);
});
} else {
// 選択肢1. lazyLoadライブラリを使う
const script = document.createElement("script");
script.async = true;
script.src = "https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.1.0/dist/lazyload.min.js";
window.lazyLoadOptions = {
elements_selector: "[loading=lazy]"
};
document.body.appendChild(script);
// 選択肢2. Insersection Observerを使う
if ('IntersectionObserver' in window) {
const observer = new window.IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const lazyImage = entry.target;
lazyImage.setAttribute('src', lazyImage.dataset.src);
observer.unobserve(lazyImage);
}
});
});
lazyImages.forEach((lazyImage) => {
observer.observe(lazyImage);
}
} else {
lazyImages.forEach(function (lazyImage) {
lazyImage.setAttribute('src', lazyImage.dataset.src);
});
}
}
})();
</script>
NOTE: 画像を読み込む間にプレースホルダーを表示するProgressive renderingという手法があります。プロジェクトの特性に応じて必要か選択して利用してください。テキスト主体で画像がメインと考えられない場合は、表示領域を確保し、CSSや軽量の共通画像で背景を指定するだけで十分かもしれません。フォトギャラリーなど画像の表示がサイトのコンバージョンに影響する場合には、Progressive JPEG、LQIP,SQIPなどを利用すると良いでしょう。ただし処理が多くなり表示の遅延につながる可能性があるため、控えめに利用してください。
2. Webフォント
Webフォントはデザインされたフォントでコンテンツを表示できることから、見た目上のメリットがありますがその反面、パフォーマンス上のデメリットも大きいので、慎重に利用します。特に日本語フォントは文字数も多いため、積極的に利用するのは難しいでしょう。
🚨 ✅ 🎨 2.1 Ameba共通ルールに従っているか
Amebaで提供するフォントに関しては、以下のルールに従っているか確認してください。
- 数字のフォントがそのコンテンツにとって極めて重要な場合は、Ameba Numbers(数字のみ収納されたWebフォント)を利用可能です。Webフォントとして利用する際には、デバイスフォントでも表示が可能なように設計し、CSSで
font-display: optional
を指定します。ただし利用する数字が少ない場合(例えばランキング用途で1から3まで)は、SVGかデバイスフォントで表示します - それ以外の英字、記号、日本語を含むフォントは個別文字でSVG化して利用します
- ただし、後述するサブセット化が可能な場合は、ファイルサイズに注意して利用します
✅ 2.2 サブセット化されているか
Webフォントは、表示に必要な文字だけを含むフォントファイルを作成して、ファイルサイズを減らせます。そうすると、表示が早くなるほか、無駄なネットワーク転送量も減らせます。
例えば、Google Fontsではtextパラメータで表示に必要な文字を指定できます。
https://fonts.googleapis.com/css2family=Comfortaa&text=Hello
3 レイアウトシフト
レイアウトシフトは要素のサイズ変更を起点に、画面内のレイアウトが変更されることです。これは誤タップや、閲覧している要素が画面外へ消えてしまうなどユーザー体験を著しく損なう原因となります。これらが何度も発生すると、コンバージョンの低下やサービスへの信頼感の低下につながるため、できる限りなくしたいものです。
スクロールして記事を読んでいる際に、画面上部に遅れて大きな要素が表示されるとレイアウトシフトが起きます。利用者は、どこを読んでいたのかわからなくなってしまい、またその位置へ戻るという無駄な手間をかけなければなりません。
✅ 3.1 要素のサイズを指定する
画像や埋め込みオブジェクトなどは縦横比がわかるように、縦サイズ、横サイズを指定しましょう。
<!-- 画像や埋め込み要素には縦サイズ(height)と横サイズ(width)を必ず指定します -->
<img height="200" width="300" alt="" src="image.png">
<iframe height="400" width="300" title="" src="frame.html"></iframe>
要素のサイズを指定すると、予め表示領域が確保されます。要素の取得が遅くても、レイアウトは変わらず表示されます。 要素のサイズを指定しないと、要素の取得時に表示領域が確保され、画面内のレイアウトが大きく変更されます。
DO: 要素のサイズを指定すると、予め表示領域が確保されます。要素の取得が遅くても、レイアウトは変わらず表示されます。
DON'T: 要素のサイズを指定しないと、要素の取得時に表示領域が確保され、画面内のレイアウトが大きく変更されます。
✅ 🎨 3.2 複数サイズの要素が入る領域には、最大サイズに合わせてレイアウト指定する
例えば、100pxと250pxの高さの要素が表示される領域には、最大値の250pxに上下余白を加えた合計値を高さとして指定する必要があります。
Amebaでは広告向けにこの対応をしていますが、表示されうる広告のサイズが変わるたびに見直す必要があります。
DO: 複数サイズの要 素が表示される可能性のある領域では、大きい方に合わせて余白を指定します。
DON'T: 小さいサイズに合わせて余白を指定すると、大きいサイズの要素が表示された時にレイアウトシフトが起こります。
DO: リストを利用する場合には、表示したい件数分の領域を確保します。情報設計、UI設計の段階から考慮しましょう。
DON'T: あらかじめ3件分の要素が確保されているリストに、5件の内容を表示するとレイアウトシフトが起こります。
✅ 🎨 プレースホルダー(取得前、取得中)やフォールバック(0件、取得失敗)コンテンツを設定する
データ取得前、データ取得中、データ取得後、エラー時などの状態で要素の表示内容が変わる場合にも、レイアウトシフトを防ぐために同じサイズの領域内でコンテンツを表示します。
このレイアウトシフトが起こりやすい要素としては、0件とそれ以外の件数で表示が異なる要素、状態に応じて表示を変えるボタンなどがあります。
DO: データ取得が失敗した時には、要素の領域はそのままで、詳細の情報を表示します。
DON'T: データ取得が失敗した時に、要素ごと消してしまうと、レイアウトシフトが起こります。
複数の状態 でも同じサイズで要素を表示できるようにします。レイアウトシフトは縦方向だけでなく、横方向にも注意します。
DO: 非ログイン時にはログインボタン、ログイン時には同じサイズでサムネイルとニックネームを表示します。
DON'T: ログイン中であった時に、ログインボタンだけ消すとレイアウトシフトが発生します。
✅ 🎨 インフィニティスクロールする場合には、プレースホルダーを用意し、下に要素を配置しない
インフィニティスクロールを使ってコンテンツを追加する際、その下に要素があるとレイアウトシフトします。例えば、フッターがページ下部にあるとインフィニティスクロールをすると、その要素が操作できなくなってしまいます。
これらを解決するためには、まずそのページでインフィニティスクロールが必要か考慮します。多くの情報が必要とされていない場合や、データ転送量が多いと想定される場合には、ページングへ変更することを考えてもよいでしょう。
インフィニティスクロールを改善する場合には、下に要素を配置せず他の表示方法にします。例えば画面ページ下部に固定して表示されるようにしたり、メニュー内など他の場所から参照できるようにしたりします。
さらに、インフィニティスクロールで表示されるコンテンツを早めに取得したり、プレースホルダーを用意したりして画面に何も表示されない時間をなくします。
その他にも、それ以上スクロールしてもコンテンツが無い最後のタイミングでフッターを表示する方法も考えられます。
DO: インフィニティスクロールで要素を表示する際には、下部に要素を配置しないようにします。また読み込み中の要素にもプレースホルダーとともに固定サイズを指定します。
DON'T: インフィニティスクロールする要素より下の要素は、各要素が表示されるタイミングで画面から消えてしまうため、操作できなくなります。
✅ Webフォントを適切に読み込む
代替フォント(デバイスフォント)からWebフォントに表示が変更される際に、レイアウトシフトが起こります。ネイティブアプリのように予め端末にインストールできる場合にはこの問題は起こりません。
対策としてWebフォントを使う前に、SVGにできないか検討します。また、Webページで利用する際にはfont-display: optionalを指定し、Webフォントは取得できた時のみ表示することも有効です。
4 ネットワーク
ネットワークからのレスポンスが遅いと、最悪の場合には、画面に何も表示されないということが起こりえます。パフォーマンスを改善する際に、ネットワーク経由のレスポンス速度の最適化は根本的かつ重要な項目です。
4.1 レスポンスを遅延させる無駄な処理を省く
ネットワークやバックエンドアプリケーションにレスポンスを遅延させる無駄な処理がないか確認しましょう。例えば、多すぎるリダイレクトや不効率なネットワーク経路、サーバーアプリケーションでの時間のかかる処理、重いクエリなどです。
レスポンス速度の目安として、エンドユーザー(端末)からのリクエストに対して、100ミリ秒以下で返却完了することを目指しましょう。
4.2 キャッシュして無駄な計算処理を省く
何度リクエストしても同じ内容を返却するレスポンスは、できる限り長くキャッシュします。 効率的にデータをキャッシュするには、エンドポイントを静的(記事内容などリクエストごとに変化しないもの)、動的(ユーザー情報などリクエストごとに変化するもの)へと分類することが有効です。
また、アクセスに対して地理の影響を受けず、安定したレスポンスをできるようにします。そのため主要拠点にサーバーを配置したり、CDNを利用したりします。特に検索エンジンのクローラーからのリクエストが重要なサービスにおいては、積極的にこれらの対応をしましょう。
参考資料: CDNフル活用でつくる、高速Webアプリ
4.3 ネットワークリクエストの優先度は最適されているか
アプリケーションから発行されるネットワークリクエストが最適化されているか確認します。例えば、複数のリクエストを並列でなく全て同期的にリクエストしている、あるいはレスポンスのデータ量が大きい場合には遅延する可能性があります。
リクエストの優先度が効率的に設定されているか、返却内容から無駄なデータを省いたり、エンドポイントを分割したりデータ量を減らせないかなど確認します。
5 アプリケーション
5.1 アプリケーションのファイルサイズを小さくする
ネットワーク回線速度や端末ディスク容量は年々改善され、アプリケーションのファイルサイズ自体が大きな問題になることは少なくなっています。
しかし、アクティブなユーザーほどネットワークやディスクを消費するため、同じ内容であればできる限りファイルサイズを小さくした方がよいでしょう。利用者にとっては、表示速度やダウンロード速度、メモリ使用量、ディスク使用量など様々なメリットがあります。
パフォーマンスバジェットを設け、ファイルサイズを作業単位やリリース前後で監視することも有効です。パフォーマンスバジェットを設定する際には、競合他社と比較したり、一般的なガイドラインを目安にしたりします。
5.2 アプリケーションがクラッシュしない
アプリケーション利用時に画面が固まったり、クラッシュしてしまったりすることは利用者が行ったアクションを中断し、ストレスを与えてしまいます。
アプリケーションの状態が再現できるようにした上で、エラーログは集計され、クラッシュフリー率は99%以上であることが望ましいでしょう。
5.3 UIスレッドを必要以上に長期間専有しない
UIスレッドを長時間専有する処理があると、画面が固まったり、スクロールが遅延したりと快適な閲覧ができなくなってしまいます。
無駄な処理を省いたり、処理自体を細かく分けたり、他のスレッドに処理を移したりするとよいでしょう。