Geek #17

決定的なフロントエンドのパフォーマンスガイドを作成するのに、大規模サイトの開発をしている熟練者たちを集められたらどうでしょう?

ロボット向けに作られた退屈なガイドとは違って、何か面白いものを作ったらどうでしょう? 最高のリファレンスを作ることを可能にするために、Briza Bueno (Americanas.com)Davidson Fellipe (Globo.com)Giovanni Keppelen (ex-Peixe Urbano)Jaydson Gomes (Terra)Marcel Duran (Twitter)Mike Taylor (Opera)Renato Mangini (Google)Sérgio Lopes (Caelum) とともにやっていくのはどうでしょう?

それこそまさに私たちがやってきたことです!そして私たちはより高速なサイトを作る戦いへ案内します。

Zeno Rocha、プロジェクトリーダー。

パフォーマンスが本当に重要でしょうか?

??

もちろんあなたも知っているように重要です。 では、なぜ私たちは酷いユーザーエクスペリエンスへとつながる遅いサイトを作り続けるのでしょうか? これはより高速なサイトの作り方を見せるコミュニティ駆動型の実践的なガイドです。 億万長者のパフォーマンスの事例を見て時間を浪費するのを止めて、本題に入りましょう!

HTML

インライン/埋め込みのコードを避ける

ページにCSSかJavaScriptを読み込むには3通りの方法があります。

1) インライン: どのHTMLタグでも、CSSはstyle属性で定義され、JavaScriptは例としてonclick属性に記述されます;

2) 埋め込み: CSSは<style>の中で定義され、JavaScriptは<script>タグの中で定義されます;

3) 外部ファイル: CSSは<link>から読み込まれ、JavaScriptは<script>タグのsrc属性から読み込まれます。

最初の2つの方法は、HTTPリクエストの数を減少する一方で、HTMLドキュメントのサイズを増大します。 ですがこれは、あなたが小さなアセットしか持たないけれども、リクエストのコストがより大きい場合に有用です。 この場合には、実際にスピード面で効果があるかを評価するテストをしましょう。 また、あなたのページとそのユーザーの目的を評価しましょう: もし、訪問者が二度と戻らないと想定される期間限定のキャンペーンのように、一度だけユーザーが訪れることを想定するなら、インライン/埋め込みのコードはHTTPリクエストの数を減少させる助けになるでしょう。

> HTMLの中で手作業でCSS/JSを編集するのは避けましょう(ツールを用いてこの行程を自動化することが望ましいです)。

3つ目の方法は、あなたのコードの構成を改善するだけでなく、ブラウザにキャッシュさせることも可能になります。この方法は、ほとんどの場合に望ましい方法であり、大きなファイルや大量のページを扱うときには特に望ましいです。

> 有用なツール / リファレンス

styleは上へ、scriptは下へ

スタイルシートを<head>の中に置くとき、ページを徐々にレンダリングすることが許容され、ユーザーに対してページが高速に読み込まれている印象を与えます。

<head>
  <meta charset="UTF-8">
  <title>Browser Diet</title>

  <!-- CSS -->
  <link rel="stylesheet" href="style.css" media="all">
</head>

もしページの最後にそれらを置いた場合、ページはCSSがダウンロードされて適応されるまでの間スタイルなしでレンダリングされます。

Geek #32

一方で、JavaScriptについては、scriptが読み込まれて実行される間にレンダリングをブロックするので、ページの最後に配置することが重要です。

<body>
  <p>Lorem ipsum dolor sit amet.</p>

  <!-- JS -->
  <script src="script.js"></script>
</body>

> リファレンス

asyncを試みる

この属性が、よりよいパフォーマンスのために有用であることを説明するには、これを使わないときに何が起きるかを理解した方がいいでしょう。

Geek #20
<script src="example.js"></script>

この形式だとページは、それ以降のHTMLの解析とレンダリングをする前にscriptが完全にダウンロード、解析、実行されるのを待つ必要があります。 これはロード時間を大幅に増やすことになります。 時にはこの振る舞いが望ましいことがあるかもしれませんが、一般的には違います。

<script async src="example.js"></script>

scriptは、ページの残りの部分を解析している間に非同期でダウンロードされます。 scriptはダウンロードが完了したらすぐに実行されることが保証されます。

> リファレンス

CSS

スタイルシートをミニファイ

可読性の高いコードを保つためには、コメントやインデントは良いアイディアです。

.center {
  width: 960px;
  margin: 0 auto;
}

/* --- Structure --- */

.intro {
  margin: 100px;
  position: relative;
}

ですがブラウザにとってはそれは重要ではありません。従って、自動化ツールを使ってCSSをミニファイすることを常に覚えておいてください。

.center{width:960px;margin:0 auto}.intro{margin:100px;position:relative}

それがファイルサイズを削ることになり、高速なダウンロード、解析、実行へとつながります。

SassLessStylusのようなプリプロセッサーを利用することで、ミニファイされたCSSをコンパイル時に出力するように設定することが出来ます。

> 有用なツール / リファレンス

複数のCSSファイルの結合

styleの構成とメンテナンスのための別のベストプラクティスは、モジュールの構成要素に分割することです。

Geek #9
<link rel="stylesheet" href="structure.css" media="all">
<link rel="stylesheet" href="banner.css" media="all">
<link rel="stylesheet" href="layout.css" media="all">
<link rel="stylesheet" href="component.css" media="all">
<link rel="stylesheet" href="plugin.css" media="all">

ですが、これらのファイル毎にHTTPリクエストが必要になります(そして私たちはブラウザが並列で限られた数のリソースのみをダウンロードすることを知っています)。

<link rel="stylesheet" href="main.css" media="all">

なのでCSSを結合しましょう。少数のファイルで構成することはリクエストの数を少なくすることになり、より高速にページを読み込みます。

いずれの長所も欲しい場合はどうでしょう? ビルドツールを使ってこのプロセスを自動化しましょう。

> 有用なツール / リファレンス

@importよりも<link>

ページに外部ファイルのスタイルシートを読み込む方法は2つあります: 一方は<link>タグ:

<link rel="stylesheet" href="style.css">

または@importです(外部のスタイルシートか<style>タグの中で)。

@import url('style.css');

外部ファイルで2番目の方法を用いると、ほかのアセットのダウンロードをブロックすることになり、ブラウザは並列でアセットをダウンロードすることができません。

> リファレンス

JavaScript

サードパーティのコンテンツの非同期読み込み

Youtubeのビデオやlike/tweetボタンを埋め込むためのサードパーティのコンテンツを読み込んだことがない人がいますか?

Geek #46

これらのコードの大きな問題は、ユーザーの接続環境かホスティングされているサーバーの接続状態によって、常に効果的に配信される訳ではないということです。また、サービスが一時的にダウンしていたり、ユーザーかユーザーの会社のファイアウォールによってブロックされていることさえ有り得ます。

これがページの読み込み時に大きな問題とならないように、ページ全体の読み込みをロックするには、常にこれらのコードを非同期で読み込みましょう(もしくは Friendly iFrames を使いましょう)。

var script = document.createElement('script'),
    scripts = document.getElementsByTagName('script')[0];
script.async = true;
script.src = url;
scripts.parentNode.insertBefore(script, scripts);

あるいは、複数のサードパーティウィジェットの読み込みたい場合、このscriptを用いて非同期で読み込ませることができます。

> 動画 / リファレンス

配列の長さをキャッシュする

ループは確実にJavaScriptのパフォーマンスに関する最も重要な部分の1つです。反復処理が効果的に行われるようにループの内部のロジックを最適化しましょう。

1つの手段は、包まれる配列のサイズを保持しておくことで、それによってループが繰り返される度に再度計算する必要がなくなります。

var arr = new Array(1000),
    len, i;

for (i = 0; i < arr.length; i++) {
  // 良くない - サイズは1000回計算される必要があります
}

for (i = 0, len = arr.length; i < len; i++) {
  // 良い - サイズは1度だけ計算されて保持されます
}

> JSPerfの結果

> 注: モダンブラウザのエンジンは自動的にこのプロセスを最適化しますが、未だに残るレガシーブラウザ向けの良いプラクティスとして残しています。

例えばdocument.getElementsByTagName('a')によって生成されたNode(NodeList)のリストのようなHTMLの集合に対する反復処理の場合に、これはとりわけ重要です。これらの集合は"生きている"と見なされます。即ち、それらはその属する要素の変化に応じて自動的に更新されるのです。

var links = document.getElementsByTagName('a'),
    len, i;

for (i = 0; i < links.length; i++) {
  // 良くない - リンクのリストに変化があるかを確認するため反復処理毎に再計算されます
}

for (i = 0, len = links.length; i < len; i++) {
  // 良い - リストのサイズを最初に取得して保存し、反復毎に比較します
}

// 恐ろしい: 無限ループの例
for (i = 0; i < links.length; i++) {
  document.body.appendChild(document.createElement('a'));
  // リンクのリストが増加する反復処理毎に、ループの終了条件が満たされることがありません
  // リストのサイズが保持され条件として使われる場合には起きないことです
}

> リファレンス

document.writeを避ける

document.writeを使用することは、ページが完全に読み込まれるその戻りでページへの依存を引き起こします。

この(悪い)プラクティスは開発者たちによって長年にわたって使用されなくなりましたが、幾つかのJavaScriptファイルのための同期フォールバックとして、必要なケースで未だに利用されています。

例えば、HTML5 Boilerplateでは、GoogleのCDNが応答しない場合にローカルのjQueryを読み込むテクニックとして使っています。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.0.min.js"><\/script>')</script>

> 注: window.onloadの間か後にdocument.writeを実行するのはページの全コンテンツを置き換えます。

<span>foo</span>
<script>
  window.onload = function() {
    document.write('<span>bar</span>');
  };
</script>

ページの結果は予想しているfoobarではなくbarだけです。window.onloadイベントの後に実行されたときも同様です。

<span>foo</span>
<script>
  setTimeout(function() {
    document.write('<span>bar</span>');
  }, 1000);
  window.onload = function() {
    // ...
  };
</script>

予定されたsetTimeoutwindow.onloadイベントの後に実行される場合でも、結果は前述の例と同じになります。

> リファレンス

再描画と再フローを最少化

再描画と再フローは、あるプロパティか要素が変更された時に、再度レンダリングされるプロセスで引き起こされます。

再描画は、そのレイアウトが変更されることなく見た目上の変更があるときに起きます。 Nicole Sullivanは、background-colorを変更するようなスタイルの変更としてこれを説明しています。

再フローは、要素のwidthの変更のような、ページのレイアウトの変更によって引き起こされるので最も高コストです。

過度の再描画と再フローを避けるべきであることは明白ですので、このようにする代わりに:

Geek #55
var div = document.getElementById("to-measure"),
    lis = document.getElementsByTagName('li'),
    i, len;

for (i = 0, len = lis.length; i < len; i++) {
  lis[i].style.width = div.offsetWidth + 'px';
}

こうすべきです:

var div = document.getElementById("to-measure"),
    lis = document.getElementsByTagName('li'),
    widthToSet = div.offsetWidth,
    i, len;

for (i = 0, len = lis.length; i < len; i++) {
  lis[i].style.width = widthToSet + 'px';
}

style.widthを設定する場合、ブラウザはレイアウトを再計算する必要があります。ブラウザは画面を更新する必要があるまで再描画を考える必要がないので、大抵多くの要素のスタイルを変更することは、一度の再描画ですみます。

もしページのレイアウトのデータを読み取る必要があるなら、2つ目の例のようにレイアウトに変更を加える何かを設定する前に、全てを行なっておきましょう。

> デモ / リファレンス

不要なDOM操作を避ける

必要がないのにDOMを操作する度、パンダは死にます。

冗談抜きに、DOM要素のブラウジングは高コストです。JavaScriptのエンジンは強力に高速になってきってはいますが、常にDOMツリーのクエリーを最適化した方がよいでしょう。

最も簡単な最適化の1つは、頻繁にアクセスされるDOM要素のキャッシングです。 例えば、ループの反復処理毎にDOMの検索をする代わりに、一度だけ検索してその結果を変数に保持し、その変数をループの反復処理毎に使いましょう。

// 本当に良くないです!
for (var i = 0; i < 100; i++) {
  document.getElementById("myList").innerHTML += "<span>" + i + "</span>";
}
// ずっといいです :)
var myList = "";

for (var i = 0; i < 100; i++) {
  myList += "<span>" + i + "</span>";
}

document.getElementById("myList").innerHTML = myList;
// ずっとずっといいです :)
var myListHTML = document.getElementById("myList").innerHTML;

for (var i = 0; i < 100; i++) {
  myListHTML += "<span>" + i + "</span>";
}

> JSPerfの結果

scriptのミニファイ

CSSのように、可読性の高いコードを保つためには、コメントやインデントは良いアイディアです。

BrowserDiet.app = function() {

  var foo = true;

  return {
    bar: function() {
      // do something
    }
  };

};

ですがブラウザにとってはそれは重要ではありません。従って、自動化ツールを使ってJavaScriptをミニファイすることを常に覚えておいてください。

BrowserDiet.app=function(){var a=!0;return{bar:function(){}}}

それがファイルサイズを削ることになり、高速なダウンロード、解析、実行へとつながります。

> 有用なツール / リファレンス

複数のJSファイルを1つに結合する

scriptの構成とメンテナンスのための別のベストプラクティスは、モジュールの構成要素に分割することです。

Geek #47
<script src="navbar.js"></script>
<script src="component.js"></script>
<script src="page.js"></script>
<script src="framework.js"></script>
<script src="plugin.js"></script>

ですが、これらのファイル毎にHTTPリクエストが必要になります(そして私たちはブラウザが並列で限られた数のリソースのみをダウンロードすることを知っています)。

<script src="main.js"></script>

なのでJSを結合しましょう。少数のファイルで構成することはリクエスト数を少なくすることになり、より高速にページを読み込みます。

いずれの長所も欲しい場合ばどうでしょう? ビルドツールを使ってこのプロセスを自動化しましょう。

> 有用なツール / リファレンス

jQuery

常に最新のバージョンのjQueryを使用する

jQueryの開発チームは、より良い可読性、新たな機能と既存アルゴリズムの最適化などを通じて、常にライブラリの改善をもたらしています。

Geek #36

このため、以下URLからコピーして利用できる最新バージョンのjQueryを常に利用しましょう。:

http://code.jquery.com/jquery-latest.js

ですが、テストをする機会を持てずに新しいバージョンが自動的にあなたのサイトに配信される問題が生じるので、決して<script>タグでこのURLを参照してはいけません。 代わりに、あなたが特に必要としているjQueryの最新バージョンにリンクしましょう。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

賢明なBarney Stinsonが言ったように、"新しいものは常に優れている" :P。

> リファレンス

セレクター

セレクターはjQueryの使用上最も重要な問題の1つです。 DOMから要素を選択するにはclassやIDを利用したりfind()children()のようなメソッドを用いるなど、多様な方法がありますが、それらが同様のパフォーマンスであることを意味している訳ではありません。

それら全ての中で、IDを選択することがネイティブのDOM操作に基づくので最速となります。

$("#foo");

> JSPerfの結果

eachの代わりにforを使用する

生のJavaScriptの機能はほぼ常にjQueryのそれよりも高速です。そのため、jQuery.eachを使用する代わりにJavaScriptのforループを使用しましょう。

ですが注意してほしいのは、for inはネイティブですが、ほとんどのケースでjQuery.eachよりも性能が低いです。

そしてよくテストされたforループは、反復処理において集合の長さをキャッシュしておくことでパフォーマンスより良くする機会を与えてくれます。

for ( var i = 0, len = a.length; i < len; i++ ) {
    e = a[i];
}

反対のwhileforループはコミュニティにおいてホットなトピックで、反復処理の最速な形式として頻繁に引用されます。 ですが、可読性の低下を理由に一般的には使用されません。

// reverse while
while ( i-- ) {
  // ...
}

// reverse for
for ( var i = array.length; i--; ) {
  // ...
}

> JSPerfの結果 / References

jQueryを常に使用しない・・・

時に生のJavaScriptはjQueryよりも容易でより高性能です。

Geek #6

以下のようなHTMLがあったとして:

<div id="text">Let's change this content</div>

このようにする代わりに:

$('#text').html('The content has changed').css({
  backgroundColor: 'red',
  color: 'yellow'
});

生のJavaScriptを使用しましょう:

var text = document.getElementById('text');
text.innerHTML = 'The content has changed';
text.style.backgroundColor = 'red';
text.style.color = 'yellow';

この方がシンプルで より高速です。

> JSPerfの結果 / リファレンス

Images

CSSスプライトを使用する

このテクニックは多様な画像を全て1つのファイルにグルーピングします。

CSS Sprite Example

そしてCSSでそれらを配置します。

.icon-foo {
  background-image: url('mySprite.png');
  background-position: -10px -10px;
}

.icon-bar {
  background-image: url('mySprite.png');
  background-position: -5px -5px;
}

結果的に、あなたは劇的にHTTPリクエストの数を減少させ、ページの他のリソースの滞在的な遅れを避けることになります。

スプライトを使うとき、あまりに多くの画像間の空白を残すことは避けましょう。ファイルサイズへの影響はありませんが、メモリーの消費量に影響があります。

ほぼ全ての開発者がスプライトについて知っているのに反して、このテクニックは広く使われていません—おそらくスプライトを生成するのに自動化ツールを使用していないからでしょう。このことに関してはあなたを助ける幾つかを強調してきました。

> 有用なツール / リファレンス

Data URI

このテクニックはCSSスプライトの代わりになります。

Data-URIは、通常はURIのコンテンツのインライン化を目的とする方法です。 この例では、私たちはページの読み込みで必要となるHTTPリクエストを削減するためにCSSのbackground imageのコンテンツをインライン化するのに使っています。

Before:

.icon-foo {
  background-image: url('foo.png');
}

After:

.icon-foo {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII%3D');
}

IE8以上の全てのブラウザはbase64エンコードがサポートされています。

この方法とCSSスプライトのいずれもがメンテナンスしていくのにビルドツールを必要とします。この方法は、開発中に個別のファイルで画像を維持するのに手動でのスプライトの配置を不要とするアドバンテージがあります。

ですが、大きな画像を持っていた場合にはHTML/CSSのサイズを増大させるデメリットもあります。サイズのオーバーヘッドなどのHTTPリクエストは、HTTPリクエストの数を最少化することによるスピードの改善がなされないので、HTML/CSSをgzipしない場合には推奨されません。

> 有用なツール / リファレンス

マークアップでの画像サイズ変更をしない

画像のwidthheightの属性を常に記述しましょう。これはレンダリング中の不要な再描画と再フローを避けることにつながります。

<img width="100" height="100" src="logo.jpg" alt="Logo">

このことを知って、開発者のJohn Qは700x700pxの画像を50x50pxの画像として表示することにしました。

この開発者が理解していないことは、不要な数十キロバイトがネットワーク上で送信されることです。

常に覚えておいて欲しいのは: HTMLで画像のwidthとheightを記述できることが大きな画像のサイズを変更できることを意味している訳ではないということです。

> リファレンス

画像の最適化

画像ファイルはウェブには不要なたくさんの情報で構成されています。 例えば、JPEG写真はカメラからExifメタデータを持ちます(日付、カメラモデル、場所など)。PNGは色、メタデータの情報を持ち、時に埋め込みのサムネイルを持つことさえあります。これらのいずれもブラウザで利用されることはありませんし、ファイルサイズを増やすことにつながります。

不要なデータを削除し、画質を劣化させることなく同様の画像を生成する、画像の最適化ツールがあります。ツールを用いて欠損なしの圧縮ができます。

画像最適化の別の方法は、視覚的な質を犠牲にして圧縮することです。 私たちはこれを損失のある最適化と呼びます。例えば、JPEGをエクスポートするときに品質レベル(0から100の数値で)を選ぶことができます。パフォーマンスを考えて、視覚的に許容範囲の中で最も低い数値を選びましょう。その他の損失のある最適化テクニックはPNGのカラーパレットを減少するかPNG-24のファイルをPNG-8に変換することです。

ユーザー体感のパフォーマンスを改善するために、JPEGファイルをプログレッシブJPEGへ変換すべきです。プログレッシブJPEGはブラウザのサポートが充実していて、作成が容易で、パフォーマンス面での大きな問題もありません。この利点はページに素早く画像を表示できることです(デモ)。

> 有用なツール / リファレンス

Bonus

診断ツール: あなたの親友

Geek #43

あなたがウェブパフォーマンスの世界に挑もうと思うのであれば、ブラウザにYSlow の拡張機能をインストールすることは極めて重要です—その瞬間から彼らはあなたの親友となります。

それか、オンラインツールを希望するのであれば、WebPageTestHTTP ArchivePageSpeedのサイトを訪れてください。

一般的にそれらはあなたのサイトのパフォーマンスを解析し、あなたのサイトに対する滞在的な問題を解決して助けてくれる非常に貴重なアドバイスを伴った成績のレポートを作成してくれます。

今日はこれでおしまいです!

Geek #31

このガイドを読み終えた後、あなたがサイトを整備できることを、私たちは期待しています。 :)

そして覚えておいて欲しいのは、人生におけるすべてのことと同じように、銀の弾などないということです。 あなたのアプリケーションのパフォーマンスチューニングは価値のある冒険ですが、すべての開発における決定の唯一の基礎とすべきではありません。

より深く学びたいと思いますか? 私たちがこのガイドを作成するのに利用したリファレンスを確認してください。

提案がありますか? @BrowserDietにツイートを送るか、githubでプルリクエストを送ってください。

あなたの友人たちと共有することを忘れずに、みんなのためにより速いウェブを作りましょう。\o/