ふろしき Blog

コンテンツサービスを科学する株式会社ブートストラップ代表のブログ

1300の優良サイトが選んだ「CSSリセット」のベストプラクティス

1300の優良サイトが選んだシリース、第2回は「CSSリセット」です。

CSSリセットとは、WebブラウザごとにCSSのデフォルト値が異なるという問題を、解決するための手法のことです。

インターネットはマルチブラウザに対応することが求められています。ピクセル単位の細かいデザインもWebではご法度ですが、想定外の動作を防ぐためにも必要な知識と言えます。

今回も、1300の優良サイトが採用しているCSSリセット手法を確認し、ベストプラクティスが何かを探ってみようかと思います。

具体的にどんな問題があるのか?

IE6、IE8、現時点の最新のChrome、Firefoxのデフォルト値と、実際の表示状態を確認してみましょう。以下はbodyタグに設定されたCSSプロパティの値です。

f:id:furoshiki0223:20131012164003p:plain

コンテンツの余白を意味するmarginですが、IE6の「左:10px/上:15px」に対して、それ以外のWebブラウザは「左:8px/上:8px」と狭い値を採用しています。また、行の高さも、Firefoxの設定値が微妙に他とは異なるため、余白の位置は同じなのにコンテンツの終端が異なるといった現象が発生しています。

これ以外にも、h1〜h5のサイズや余白が微妙に異なったり、Tableやulのレイアウトが異なるなど様々な「異なるデフォルト値」が存在しているため、CSSで明示的に値を設定し、統一化させることが求められます。

ふろしき.jsの調査では、1300中97サイト(7.46%)にて、CSSライブラリによる対策が行われていることを確認しました。本記事では、その97サイトで利用されている全CSSライブラリを網羅的に紹介させて頂きます。

reset.css / YUI Library (76件)

YUI Libraryに含まれているCSSリセットライブラリです。上記の統計情報はプレーンなreset.cssの集計結果ですが、改造したり部分的にノウハウを流用したり他のライブラリ・テンプレートに組み込まれたりと、様々な方法で利用されているようです。

Normalize.css (11件)

reset.cssよりも多くの要素に対し厳格な値を設定したもので、こちらも他のライブラリ・テンプレートに含まれていることが多いです。YUI3、Twitter Bootstrap、HTML5 Boilerplateなど、様々なライブラリ・テンプレートに含まれており、上記統計情報には表れませんが、高い信頼を得ています。

html5reset.css (10件)

reset.cssをHTML5の新しい要素に対しても有効になるよう設定し、centerやbigなどの削除された要素を配慮していないものです。

ベストプラクティスは?

上記の統計情報としては少ないですが、「Normalize.css」は現在進行形で多くのライブラリ・テンプレート開発コミュニティから支持を得ており、組み込んで利用されています。したがって、最も安定しているという判断ができるでしょう。現時点でのベストプラクティスと言えます。

reset.cssの採用数は多いですが、古いWeb標準のものであるため、今後採用はされなくなるように思えます。

Web標準はデフォルト値まで厳格決めていないことが殆どなので、このようなCSSライブラリの力は、今後暫くの間も必要になってくるでしょう。

1300の優良サイトが選んだ「PNG Fix」のベストプラクティス

1300の優良サイトを調査してみたシリーズ、第1回は「PNG Fix」です。

PNG Fixとは、Internet Explorer5〜8が、PNGの表示を正しく扱えないという問題を解決するための手法のことです。

インターネットにおいては、IE8が未だに高いシェアを持ち対応を求められます。そして、PNGの持つ表現力は、他のフォーマットでは代替が難しいという状況です。ビジュアルデザインを重視したWebサイト・アプリ制作を行う現場の人は、必ず覚えておきたい基本の知識と言えます。

今回、1300の優良サイトが採用しているPNG Fix手法を確認し、ベストプラクティスが何かを探ってみようかと思います。

IEには具体的にどんな課題があるのか?

IE5〜6は、PNGファイルが透明度(Alpha値)を正しく反映して表示することが出来ないという問題を持ちます。以下のように、透明な背景を灰色で塗りつぶした状態で表示させてしまいます。
f:id:furoshiki0223:20131011165026p:plain

IEの7〜8で、上記の問題は解決されたかのように思えました。しかし、CSSで透明度(Opecity)を与えると、PNGファイル内の透明度(Alpha値)が0でも255でもない半端な状態の場合、正しい解釈が行えなくなるという別の問題が発生します。

これは、最近のWebサイトでは定番となったフェードイン・フェードアウト処理の、弊害になることがあります。
f:id:furoshiki0223:20131011165236p:plain

ふろしき.jsの調査では、1300中247サイト(19.00%)にて、JSライブラリによる対策が行われていることを確認しました。本記事では、その247サイトで利用されている全JSライブラリを網羅的に紹介させて頂きます。

DD_belatedPNG (157件)

IE5〜8へのPNG Fix対応を行う、世界シェアトップのライブラリです。background-imageプロパティで指定されたPNGファイルもFixされます。CSSセレクタを利用して指定をしますが、表記がやや独特というデメリットを持ちます。

CSSセレクタを利用して、対象を指定します。

<img src="hoge.png" class="hoge" />

DD_belatedPNG.fix('.hoge');

jQuery belatedPNG (30件)

DD_belatedPNGをjQueryで利用できるように改良した、和製のプラグインです。シンプルなWebサイトでは、PNG FixのためにわざわざjQueryを使わなくてはいけないというのはデメリットでしょう。しかし、CSS3の見慣れたセレクタ表記を使えるのは、jQueryが一般化した今となってはメリットとも言えます。

<img src="hoge.png" class="hoge" />

$(".hoge").fixPng();

jQuery pngFix (17件)

IE5.5-6へのPNG Fix対応を行います。ただ、筆者の環境ではマニュアル通りに設定しても上手く動作しませんでした。上位2つのライブラリで代替可能な上、しかも対応が手厚いため、わざわざこれを選択する理由も弱いように思えます。

iepngfix (12件)

IE5.5-6へのPNG Fix対応を行います。ライセンスの面で見ても上位2つの方がメリットも大きいため、選択する理由は弱いでしょう。

alphafilter.js (10件)

IE5.5-6へのPNG Fix対応を行います。JavaScriptへの記述が不要という特徴を持つため、プログラミングが苦手なデザイナの方には利便性が高いように思えます。ただアイデアは素晴らしいですが、IE7-8のPNG Fixには対応していなかったり、「deffer」を必ず与えないと動作しなかったりと、他より劣る部分も多いようです。

<img src="./sample.png" class="alphafilter" alt="" />

unitpngfix.js (8件)

IE6へのPNG Fix対応を行います。上位2つで代替できる上、GPLライセンスであるため、選択する理由は弱いように思えます。

pngfix.js (8件)

IE5.5-6へのPNG Fix対応を行います。売りのロールオーバ対応も上位2つで対応されており、ライセンスが不明であることから、選択する理由は弱いように思えます。

iepngfix_tilebg (5件)

iepngfixを拡張し、CSS周りの問題点を解消したライブラリです。これも、上位2つで代替可能で、ライセンス面で見てもデメリットが大きいように思えます。

ベストプラクティスは?

PNG Fix自体は要求はシンプルで、差別化もそこまでされないようです。機能面、ライセンスの面でも最もメリットの大きい、上位2つのライブラリ「DD_belatedPNG」「jQuery belatedPNG」のどちらかを使うのがベストプラクティスでしょう。

このような対策のためにライブラリを使わなくてはいけないというのは非常に残念です。しかし、IE9以降は大きく改善され、他のモダンブラウザと遜色ないPNGの表示を行えるようになりました。普及するまで、もう暫くの辛抱です。

ナビゲーションバーを作るためのベストプラクティス(clearfix法とinline-block法)

横並びのブロック要素の作成はWeb技術入門者にとっての最初の難関であり、その考え方を理解するのに多くの時間を要します。テーブルレイアウトに逃げてしまう元凶でもあります。

ボタンを左から右に向かって並べるナビゲーションバーなんて、まさにその好例です。

f:id:furoshiki0223:20130923012742p:plain

どのようにマークアップすべきか?

かつて「テーブルレイアウト」と呼ばれる手法が一般的だった頃、この実装は「table要素」を使うのがスタンダードでした。

f:id:furoshiki0223:20130923023618p:plain

しかし「フルCSSレイアウト」が推奨される現在では、このやり方はご法度です。Webにはセマンティクスという考え方があり、table要素は表を作る以外の目的に使うことが推奨されません。検索エンジンのクローラーや視覚障がい者に優しくない、つまり「アクセシビリティ」の低いマークアップと評価されます。

HTML4.01/XHTML1.0で作るのであれば、以下のマークアップが推奨される方法でしょう。

f:id:furoshiki0223:20130923013127p:plain

div要素を使う人も多いですが、よりセマンティクスにマークアップしたいという人はul/liを使って記述する印象があります。

HTML5であれば、ナビゲーションを表す専用のタグがあります。少し非効率的ですが、ナビゲーションバーは以下のように記述すべきです。

f:id:furoshiki0223:20130923022726p:plain

ただし、この方法はIE9以上でしか動作しません。インターネット上のWebコンテンツ製作で利用するには、もう少し時間が必要でしょう。

ナビゲーションバー用途のリンクは、マウスオーバー時にビジュアルデザインを変えるのが普通です。aタグの:hoverが活用できると、JavaScriptを利用せずにインタラクティブなビジュアルを実現できます。

したがって、2013年の現時点では、以下のマークアップが最小で効率的です。

f:id:furoshiki0223:20130923020114p:plain

float:leftの問題点

フルCSSレイアウトで横並びのナビゲーションバーを作る場合、ブロック要素に対してCSSのfloatプロパティにleftを指定します。例えば、以下のような構造を持つHTMLドキュメントでは・・・

f:id:furoshiki0223:20130923024723p:plain

<div class="nav">
  <a class="item" href="#">項目1</a>
  <a class="item" href="#">項目2</a>
  <a class="item" href="#">項目3</a>
  <a class="item" href="#">項目4</a>
</div>

CSSは、以下のように指定します。

.nav>.item {
    display : block;
    float : left;
    ~
}

「.nav>.item」で、navクラス内のitemクラスに限定して、スタイルを適用させています。

a要素はそのままだと「インライン要素」と呼ばれる、テキスト中での利用を想定した動作をしてしまいます。これを、ボタンや記事表示枠といった、画面を構成するコンポーネントとして動作する「ブロック要素」という状態へ切り替えを行う必要があります。「display:block;」が、その指定です。

そして最も注目すべきは「float : left;」です。float:leftが指定されたブロック要素は、親のブロック要素内で左寄せが行われます。そして後続するブロック要素にもfloat:leftが指定されていた場合は、そのまま隣へ配置されます。

floatの持つこの特性を利用すれば、左から右に向かって並ぶボタンを実現することが可能になります。

f:id:furoshiki0223:20130923031150p:plain

ただ、floatには少し厄介な癖があります。元々floatはWebページ上のテキストで、画像などのイメージの配置を目的に作られたものです。新聞の記事のように、写真の周りへ文章の回り込みを行わせることを意図して作られたものです。

上記のCSSの設定だと、ナビゲーションバー直後に記述した文章が回りこんでしまいます。Webブラウザで表示すると、こんな見た目になります。

f:id:furoshiki0223:20130923032602p:plain

floatは、回り込みをして欲しくない場合に、「clear:both;」というプロパティをセットしなくてはいけません。この例では、あとに続くテキストを含んだ要素に対して、clear:bothを指定する必要があります。

つまり、こんなイメージです。

f:id:furoshiki0223:20130923033807p:plain

clear:bothの設定により、ナビゲーションバーの後のテキストは、回り込まれること無く下に表示されます。

f:id:furoshiki0223:20130923034934p:plain

独立した2つのコンポーネント間で、互いにCSSの設定が依存し合うというのは、デザインとしてあまり美しくありません。

今回のケースでは、「ナビゲーションバー」と「記事」という2つコンポーネントの間に別のコンポーネントを埋め込みたくなった場合、「ナビゲーションバー」のデザインに引っ張られて、「記事」からclear:bothを削除し、「追加対象のコンポーネント」へclear:bothを追加する必要が生じます。

「ナビゲーションバー」と後に続く「記事」は、独立することが望ましいでしょう。そこで、以下のようなマークアップとCSSプロパティ設定が考えられました。

f:id:furoshiki0223:20130923035331p:plain

何の情報も持たない「ダミー要素」を作るのです。ダミー要素をナビゲーションバーの一部として扱うことで、ナビゲーションバーのCSSプロパティを独立させることができます。

ただ、デザインのために無意味な要素を追加するというのは、デザインの作りこみをHTMLドキュメント上で行うことになります。ビジュアルデザインを決定する情報はCSS内で完結させるのが理想なわけですから、当然、良いデザインとは言えません。そこで・・・

clearfix法

オーストラリアのとあるWebデベロッパが、この問題を解決するCSS記述方法を考案しました。のちにこれは全世界で広く利用されることになります。その方法とは「clearfix法」です。デファクトスタンダードなので、なぜclearfixがclearfixなのか、考えたことすらない人もいるでしょう。

先ほどの例では、HTMLドキュメント上でダミー要素を使ってclear:bothを設定しましたが、これをCSS側で擬似要素として生成して設定してしまおうというアイデアがclearfixです。他の要素に迷惑をかけずに、floatを閉じるためのベストプラクティス。

そのCSSとは、以下の通りです。

.nav>.item {
    float : left;
}
.nav:after {
    content : "";
    display : block;
    clear : both;
}

「::after」という擬似要素の指定子を利用しています。実にシンプルです。IE8からサポートされたため、動作するのはIE8以上とモダンブラウザです。

この手法、発明されたのは2004年頃らしく、当時はIE5や6でも動かすことを想定し、より複雑な記述をしていました。しかし、Web標準への準拠が高いIE8を想定するのであれば、この書き方で十分です。

IE6以上でclearfixするには?

未だにIE6を対象にしなくてはいけないケースもあるでしょう。この技術が作られた当時は、ネットスケープも対象にしていたので、今よりもっと複雑な状況です。

ここではあくまで、IE6にフォーカスを当てましょう。CSSは以下の記述です。

.clearfix {
    *zoom: 1;
}
.clearfix:after {
    content: ".";
    display: block;
    clear: both;
    height: 0;
    visibility: hidden;
    overflow: hidden;
}

前の章の「.nav」をそのまま「.clearfix」に置き換えて下さい。書き方が独特なので、多くの場合「.clearfix」という汎用的なクラスにすることで、再利用性を高めています。

様々な派生があり、例えばclearfixをwidth:100%にする亜種も発見されています。ただ、ナビゲーションバーそのもののビジュアルデザインを柔軟に変更できる方が有益なため、padding/marginの制約が小さなzoom:1が広まっているように思えます。

頭にアスタリスクの付いているzoomは、::afterに対応していない古いIE対策です。アスタリスクハックとも言われます。IEのバグを突いて、古いIEのみで動作するCSS記述を行っています。Webブラウザ固有の作りこみは悪とは言われますが、この手の定石では許されるでしょう。(古いIEだとCSS Expresstionも使えますが、奴はもっと悪です!!)

ベストプラクティスといいつつも、実際は状況に応じて最適な方を選ぶべきという答えになるかと思います。

sassでclearfixするには?

こんな感じです。凄くシンプルですね。

.clearfix {
    float : left;
    &:after {
        content : "";
        display : block;
        clear : both;
    }
}

Web標準でclearfixするには?

clearfixはデファクトスタンダードですが、最近はこれをWeb標準でなんとかしようという方向に向かっているようです。ただ、HTML5の設計面の改善はやや遅れてスタートしてて、使いもにになるにはまだ先です。

まず、clear-afterというプロパティがあります。これは2013年現在Editors Draftです。2013年に更新したようですので、これから改善が進められる段階です。

また、flex boxなる標準もあります。flexboxといえば、勧告候補にまでいきながら突如としてEditors Draftに落っこち名前ごとごっそり変わるというハチャメチャな過去を持っていますが、なんとか持ち直して勧告候補になっています。今もなお絶賛テスト中ですが、なんだかんだ言っても一番無難に使われそうな候補です。

古いIEが駆逐されないことには、使い物にならないのですが・・・。

inline-block法(※小西俊司氏のご指摘により追記。)

floatが好きになれない人には、「inline-block法」という選択肢もあります。

clearfix法では、ナビゲーションバーの各項目を明示的に左から右に向かって配置されるよう、CSS上で定義していていました。対してinline-block法では、テキストの文字は左から右に書き出されるというルールを利用して項目を配置します。

要素のdisplayプロパティに「inline-block」を指定すると、インライン要素の「テキストへ文字のように埋め込める」という特性と、ブロック要素の「縦・横幅が指定できる」という双方の特性を持ちます。プレーンなブロック要素を並べると上から下に向かって配置されますが、インラインブロック要素を並べるとテキストの行(line)の特性に従って左から右へ並べられます。(※英語・日本語が前提です。)

f:id:furoshiki0223:20130925203016p:plain
floatは要素の「場所」を指定するためのものでしたが、inline-blockは要素を「テキストの行内に配置」することを目的としたものです。そしてそこが問題になります。

例えば、改行やタブは、Webページ上でテキストとして扱う場合、1文字分のスペースとして扱われてしまいます。HTMLドキュメントのマークアップ方法にもよりますが、メンテナンス性を損なわないためにナビゲーションバーの各項目を一行で記述すると、必然として項目間に「改行やスペース」を含むことになります。

<div class="nav">
  <div class="item">項目1</div> → display:inline-block;
  <div class="item">項目2</div> → display:inline-block;
  <div class="item">項目3</div> → display:inline-block;
  <div class="item">項目4</div> → display:inline-block;
</div>

そしてこの改行やスペースは、Webページ上ではテキストという扱いとなるため、各項目間に1文字分のスペースを作ってしまいます。

f:id:furoshiki0223:20130925204239p:plain
これを埋めるために、以下のCSSを設定するのが定石です。

.nav{
    letter-spacing:-0.4em;
}
.nav>.item {
    letter-spacing: normal;
}

ナビゲーションバーの大元の部分、つまり各項目の親となる要素で、文字間隔を短くするCSSプロパティ(letter-spacing:-0.4em)を設定します。しかしこのままでは各項目内のテキストの文字幅に影響が出るため、文字間隔を元に戻す(letter-spacing: normal)という設定を行います。

するとこの通り、各項目の間のスペースがなくなります。

f:id:furoshiki0223:20130925204627p:plain

inline-block法では、配置場所を指定するためのCSSプロパティに、一見して無関係なテキストの書き出し方を指定するという泥臭い記述を行う必要があります。

clearfixとinline-block、どちらも普通なら思いつかないようなハックを行う必要があり、知らない人から見た可読性の酷さはどんぐりの背比べ状態です。ただ、inline-block法の場合は「テキストの特性」を要素の配置位置の決定要因として利用するという、完全にWeb標準の意図から外れた方法を利用しています。デザインはフルCSSレイアウト派かテーブルレイアウト派かという議論で、私は前者を勧めるタイプの人間なので、inline-block法もまた私好みのやり方ではありません。

しかし、テーブルレイアウトと同様、シンプルで直感的な記述ができるため、選択の一つとして持っておく価値はあるでしょう。どちらも方法も完全な正攻法とは言い難く、好みの問題と言わざるを得ません。

最後に

メンテナブルなナビゲーションバーを実現するclearfix法やinline-block法ですが、SEOやらアクセシビリティ的にかなり重要なHTMLドキュメントは守られても、CSSは超泥臭い本来の用途から外したある種のハックです。一時のテーブルレイアウト排除運動みたいな流れが来る気がします。ただ、テーブルレイアウトと違って相互運用性に関わるため、悪という言い方はしにくいでしょう。

clearfixに至ってはIEのバグまで使っていますが、バグを仕様と見なすのは80386なCPUから残る悪しき伝統なので、むしろ美のレベルに達している気がします。最初にこれを発見した人は、天才じゃないかとさえ思えます。

cleafix法やinline-block法は今のところ、多くのネット上のサービスではユーザCSSに含まれています。ただ個人的には、もう少し違うレイヤーに組み込まれてもいいのではと思っています。sassみたいなビルドプロセスも当たり前になりつつあるので、このあたりで上手く改善できるはずです。

古いIEが消えない状況下では、ビルドプロセスは結構熱い分野に思えます。JSPみたいなリスクもありますが、既存のWeb標準を拡張させる類のメカニズムは、Web標準そのものに良い影響を与えるはずです。

クロスブラウザ対応を助けるJSライブラリ"Modernizr"

f:id:furoshiki0223:20130629030936p:plain

Modernizrは、HTML5やCSS3などの新しいWebの標準がWebブラウザに実装されているか、汎用的なインタフェースを通じて確認することができるJavaScriptライブラリです。

▼公式サイト
http://modernizr.com
▼ダウンロード
http://modernizr.com/download/
▼Developper版(動作を試してみたい方向け。)
http://modernizr.com/downloads/modernizr-latest.js
※注:そのままでは機能が不足するため、1章〜4章の解説通りには動いてくれません!


以下は、簡単なコードのサンプルです。WebブラウザにCanvasの機能が備わっているか、確認することができます。

    <script src="./js/modernizr.canvas.js"></script>
    <script>
if( Modernizr.canvas ) {
    alert("You can use Canvas!");
} else {
    alert("You can NOT use Canvas!");
}
    </script>

▼Canvasをサポートしていない"IE8(Win7版)"の場合
f:id:furoshiki0223:20130629030407p:plain
▼Canvasをサポートしている"IE10(Win7版)"の場合
f:id:furoshiki0223:20130629030648p:plain

WindowsXPの終焉、Google Chromeのシェア獲得、iPhoneから始まったスマートデバイスの普及で、Webブラウザ環境は多様化しました。ほんの少し前まで、"インターネット=IE6"が常識で、Webは長い間、統一された一つのプラットフォームしか持たない状況が続きました。

しかし近年、Windows7の浸透により、IEは6以外のバージョンも広く普及し始めました。IEは他のモダンブラウザとは異なり、OSへの依存性が強く、古いOSでは最新のバージョンが利用できないという制約を持ちます。実際に現在のインターネット上でのWebブラウザシェアは、IE8、IE9、IE10が、それぞれ無視出来ない程度の割合で含まれています。IEだけは最新でない場合も考慮して、Webコンテンツを作る必要があるのです。

また、GoogleのChromeブラウザのシェアが急激に上昇し、Firefoxも一般ユーザへ広く浸透するようになりました。スマートデバイスの登場で、Safariなどのスマートデバイス専用のWebブラウザも、無視できない存在となりました。今Webは、統一された環境を失い、ブラウザごとにコンテンツを作りこむことが、困難な状況に陥っています。

こうした状況に置かれ、Webコンテンツの作成には、Webブラウザの機能実装を確認し振る舞いを変えるという考え方が広く普及しました。Webブラウザ・バージョンを判断軸にするのでなく、機能実装を判断軸にする。進化の早いWeb技術の中で、シンプルに後方互換性を獲得するための確かな手段です。

Modernizrはこのような、機能駆動によるWebコンテンツの実装を、理想的な形で実現するために開発されたJavaScriptライブラリです。Modernizrを使えば、IEの条件付き書式を使ったり、JavaScriptからUserAgentを取得するような処理を記述しなくても、汎用的なインタフェースを通じてWebブラウザに実装された機能を確認することができます。

1. Polyfillによる相互運用性を意識した実装

エンジニアリングの世界のうち、画面部分に特化した専門家のことを、フロントエンド技術者といいます。近年の互換性に関する問題は、正式には"相互運用性"と呼ばれ、その対策手法はフロントエンド技術者の間でノウハウが整理され、体系化が進みつつあります。

Polyfillは、HTML5やCSS3など新しいWeb技術の持つ機能が、レガシーブラウザ(主に古いIE)によって利用される必要が生じた際に、XHRやActiveXやFlashなどの古くから活用されているテクノロジーを使用し、類似した動作になるようシミュレートさせ目的を達成させるという、相互運用性に関連した技法の一つです。

以下のコードは、Modernizrを利用して、HTML Canvasが実装されていないWebブラウザで、Canvasの動作をシミュレート(Polyfill)させるため、JavaScriptライブラリ"excanvas.js"をロードしている例です。

    <script src="./js/modernizr.canvas.js"></script>
    <script>
if( ! Modernizr.canvas ) {
    Modernizr.load("./js/excanvas.js");
}
    </script>

2. プログレッシブ・エンハンスメントという設計方針

excanvas.jsはcanvasと同じインタフェースを持たせ、動作をシミュレートしてくれるJavaScriptライブラリです。先ほどの例では、Canvasが実装されていない環境下でも動作が上手くいくよう、単純に"excanvas.js"をロードさせて対処していました。

しかし、実態としてそうもいかないケースがあります。Polyfill用ライブラリは、内部では各Webブラウザに実装された独自機能を使うなど強引な手を使っているものが多く、HTML5ほどの高いポテンシャルが得られていないものが多かったりします。

そこで、古いWebブラウザの実装に合わせて機能を絞り、新しいWebブラウザについては付加価値として高いUXを実現させようという考えが生まれました。それがプログレッシブ・エンハンスメントです。

例えば、Table要素を使ったデータの一覧表を表示する機能を作ったとします。新しいWebブラウザだと、これをビジュアライズさせたグラフを表示させて、より高い付加価値を与えてたいという事例を想定してみましょう。

この場合、以下のような実装になります。

    <table>・・・</table>
    <script src="./js/modernizr.canvas.js"></script>
    <script>
if( Modernizr.canvas ) {
    Modernizr.load([
        "./js/jquery-visualizer.js",
        "./js/visualizeTable.js"
    ]);
}
    </script>

Canvasが実装されているか否かをModernizerが判定し、canvasがある環境下でのみ、jQuery-visualizerと呼ばれるtable要素の内容をグラフ化させるライブラリをロード。加えて、visualizeTable.jsという名前を与えたユーザスクリプトをロードし、グラフが表示されるようにスクリプトを実行させています。

3. グレイスフル・デグラデーションという設計方針

古いWebブラウザが混在している環境下でも、なんとかグラフを表示できないものかと考えるでしょう。jquery-visualizerは結構シンプルなグラフを吐き出すツールなので、excanvasでpolyfillしてもそこそ動いてくれたりします。ただ、お世辞にもパフォーマンスが良いとは言えません。ガッツリと処理をさせるには力不足です。

古いWebブラウザは、excanvasでそこそこに動いて貰おう。そして新しいWebブラウザでは、Canvasの機能をフル活用して、高いUXを実現しよう。高い価値を発揮させよう。Webブラウザの入替えが近くに行われるような現場などでは、そういう設計思想が歓迎されるかもしれません。新しいWebブラウザを基準にして、機能を決定するというケースです。

新しいWebブラウザでは最適で、古いWebブラウザではやや質や機能が落ちても良い、場合によっては動かないことを許容するという考えで機能設計を行う場合、これをグレイスフル・デグラデーションと言います。

実装は以下のイメージです。

    <table>・・・</table>
    <script src="./js/modernizr.canvas.js"></script>
    <script>
Modernizr.load([
    test : Modernizr.canvas,
    yep : "./js/visualizeTableExtends.js",
    nope : "./js/excanvas.js",
    both : [ "./js/jquery-visualizer.js", "./js/visualizeTable.js" ]
]);
    </script>

書き方が一気に変わったように見えます。実はModernizr.loadメソッドには、単純にJSライブラリをロードする以外にも機能があったりします。簡単な条件式を解釈し、ロードするライブラリを切り替える機能です。

ModernizrはPolyfillでの活用が前提のライブラリなので、こういった気の利いた機能がついていたりします。ちなみに構文は、以前このブログにも書いたyepnope.jsと同じです。

"test"プロパティで指定した式が真である場合、"yep"プロパティにて指定されたJSファイルがロードされます。偽である場合は、"nope"プロパティで指定されたJSファイルがロードされます。"both"プロパティはどちらであってもロードされます。

この例では、testプロパティでCanvasの実装の有無を確認し、実装がある場合は"./js/visualizeTableExtends.js"でリッチな動作を実現するユーザスクリプトをロードし、実装がない場合は単に"./js/excanvas.js"というPolyfill用ライブラリを呼んでいます。Canvasの実装の有無に関係なく、グラフ出力ライブラリである"./js/jquery-visualizer.js"がロードされ、最低限のグラフ出力処理が記述されたユーザスクリプトである"./js/visualizeTable.js"をロードしています。

レガシーWebブラウザが混在する環境下では、こうしたアプローチを行うことがあるでしょう。Modernizrは、単純にWebブラウザの実装を確認できるようにするだけでなく、相互運用性に関する多くのケーススタディをカバーする機能が実装されています。その機能は、多くの技術者により検討され考えぬかれたベストプラクティスです。対処方法に悩みが生じたなら、Modernizrに付いている機能を確認すると、その解が得られるかもしれません。

4. Polyfillの限界

Modernizrを通じてPolyfillの有用性を説明してきましたが、常にPolyfillが最善の策になるとは限りません。Polyfillはあくまで、相互運用性に関する対策技法の一つに過ぎないのです。

例えば、HTML5ではheader、footer、section、articleといった、コンテンツの意味付けを強化する(セマンティクス)、要素が追加されています。これは古いWebブラウザで正しく認識してくれません。Polyfillライブラリとしては、"html5shiv.js"が有名ですが、パフォーマンスに少々問題があります。

HTML5の学習を始めた時、一番最初に習うと言っても過言ではないセマンティクスの要素ですが、その目的はコンピュータによるコンテンツの解釈の高度化です。可読性を上げるという効果もありますが、最大の効果を発揮するのは、検索エンジンのクローラーなどにコンテンツが読み取られる場合でしょう。業務システムのようなクローズドな環境下で利用する場合、その効果を発揮できないことが多いはずです。

無理にPolyfillを使うのではなく、古くから利用されるdivやspanタグで代替するのも手です。対応Webブラウザが高いシェアを占めるその日まで、利用を封印してしまうのです。技術的理想からは外れてしまいますが、古いWebブラウザを生かさざる得ない環境下では、避ける事ができない問題は必ずといっていいほど存在します。当たり前のことですが、古いWebブラウザに新しいことをさせるのは、限界があるのです。

ただ、古いWebブラウザにより受ける制限、その理由は、後方互換性を無くすようなものであってはいけません。古いWebブラウザは、モダンブラウザに比べ非常に癖の強い動作をしますが、そこに固執しすぎることも良いことではありません。クロスブラウザの相互運用性では、全ての環境が同じように動作することは無いという前提で、設計を行うべきです。多少の動作の違いは、寛容に受け止めましょう。

5. さらにステップアップ

ここでは、Modernizrのダウンロードの手順と、他の機能について説明します。

ダウンロードページはこちらです。
http://modernizr.com/download/

f:id:furoshiki0223:20130629060443p:plain

大量のチェックボックスに尻込みしそうになるでしょう。Modernizrにはこれだけ多くのチェック機能や、Polyfillを支援する機能が備わっているのです。JavaScriptは、不要な機能を出来る限り取り外した状態にしておく必要があります。コードボリュームが大きいほど、ページ表示の応答性能が低下してしまうからです。通信量もそうですが、JavaScriptの場合はコンパイルにも時間を要します。スクリプトの1バイトは血の一滴に相当します。開発者はそれくらい、シビアにコーディングを行なっているのです。

したがって、まず初めにこの画面でやることは、全てのチェックボックスからチェックを外すことでしょう。全ての機能が取り外された、最小の状態にします。そこから最低限の必要機能だけに焦点を絞ってチェックを入れていきます。

まずこの画面の上部には、"CSS3"、"HTML5"、"Misc."、"Extra"の4つの分類があります。うち、CSS3、HTML5、Misc.の3つは、実装機能をチェックするためのものです。Extraは特殊な機能で、レガシーIEでhtml5のセマンティクス要素を追加する"html5shiv"や、先ほどから頻繁に利用しているJS/CSSのロード機能である"load"メソッド、Media QueryのPolyfillライブラリが含まれます。Polyfillを目的としているなら、"Modernizr.load"へチェックを入れましょう。

すぐ下には、"Extensibilty"、"None-core detects"の2つの分類があります。

f:id:furoshiki0223:20130629062322p:plain

"None-core detects"は、HTML5やCSS3のようなメインから外れたものを検知する機能です。HTML4の時点で既に標準化されてはいるものの、実装されていない可能性がある機能などが含まれています。

"Extensibility"は、どちらかと言えばWebブラウザやプラットフォーム開発者や、そのテスターが利用する機能でしょう。 新しいチェック用APIを追加するための機能も備えています。また、ベンダプレフィクスを付与する機能もあります。WebブラウザのユーザとしてModernizrを活用し、かつ安定した機能を利用したいと考えている場合、ここの機能は利用されることが無いでしょう。

6. Modernizr Prefixed

ExtensibilityはWebブラウザ開発者やそのテスターが利用する機能と説明しました。しかし実態として、Webブラウザが実装した機能を早期に利用したいというケースがあるでしょう。例えばCSS3は、テスト中のであるにも関わらず広く利用されている状況です。

例えば、以下のような記述を見たことは無いでしょうか?

div.comment {
   -moz-border-radius: 20px;
   -webkit-border-radius: 20px;
   border-radius: 20px;
}

これはCSSにより、枠を丸くするためのプロパティ指定です。CSSの各プロパティには、"-moz-"や"-webkit-"といったベンダプレフィクスが指定されています。IEの場合は、バージョン9から"-ms-"というベンダプレフィクスを追加し、テスト版のCSSの機能が利用できるようになりました。Operaも古いものは独自のエンジンを利用しているため、"-o-"というプレフィクスが必要でした。

CSSの場合、ベンダプレフィクス以外大抵は同じプロパティ名で動作できます。しかもパラメータの意味も同じであり、指定される値はベンダプレフィクスを除いて全く同じものを、各Webブラウザで動作するよう複数指定することになります。

"Modernizr Prefixed"は、これを一元化させることができます。例えば今回のような、border-radiusのケースでは、以下のような記述を行うことで、ベンダプレフィクスをわざわざ追加する手間を無くすことができます。

Modernizr.prefixed('borderRadius');

CSSとJavaScriptはCSSプロパティの命名規則が異なります。"border-radius"は、JavaScriptから呼び出す時は"borderRadius"とキャメルケース記法で行います。CSSプロパティ名は2つ以上の言葉を合わせることが多いため、注意が必要でしょう。

prefixedには第二引数があり、これを活用すればJavaScript内でベンダプレフィクスの付いたメソッドを呼び出すケースでも活用できます。例えば以下の例です。

window.requestAnimFrame = (function(){
    return window.requestAnimationFrame       || 
           window.webkitRequestAnimationFrame || 
           window.mozRequestAnimationFrame    || 
           window.oRequestAnimationFrame      || 
           window.msRequestAnimationFrame     || 
           function( callback ){
               window.setTimeout(callback, 1000 / 60);
           };
    })();

requestAnimationFrameは、Webブラウザのレンダリング周期に合わせ、再描画の準備が整ったタイミングでコールバックに制御を渡してくれるメソッドです。ネイティブのゲームだと必ずといって良いほど活用される機能ですが、Webブラウザでの実装は最近になってからのことです。まだベンダプレフィクスが必要な試験的機能という位置づけになります。このメソッドが動作しないWebブラウザでは、一般的なディスプレイの更新周期に合わせて、1000/60ミリ秒程度の時間差を指定して返すのが定石です。厳密にするならもう少し工夫が必要ですが、requestAnimationFrameの置き換えには十分に機能してくれます。

この機能を利用する場合、上記の例に記述した通り、大量のORを指定しなくてはいけません。これはとても見苦しいものになります。Modernizr Prefixを利用すると、以下のような記述に短縮化できます。

window.requestAnimFrame
    = Modernizr.prefixed('requestAnimationFrame', window)
        || function( callback ){ window.setTimeout(callback, 1000 / 60); };

これらの手法は、あくまでWebブラウザの試験的機能が正常に動作することが前提です。一部のWebブラウザだけ異なる動作をしてしまう場合は、破綻してしまいます。気をつけて使用して下さい。

7. Add CSS Classes

Extra内に、"Add CSS Classes"にチェックを付けると、JavaScriptだけでなく、CSSからも機能の有効性を確認するためのインタフェースを提供します。

"Add SCC Classes"を利用する場合、HTMLファイルへは、以下のような記述を行なって下さい。

<html class="no-js">

例えば、ダウンロードページで"Canvas"と"Add CSS Classes"へチェックを入れたMondertizerを読み込ませると、以下の記述に変化します。

<html class="js canvas">

これは、JavaScriptが有効かつ、Canvasが有効という意味になります。Canvasが有効でない環境、例えばIE8の場合、開発者ツールでは以下のように表示されます。

f:id:furoshiki0223:20130629214333p:plain

"no-canvas"、つまりCanvasは無効と表示されるわけです。Canvasだとあんまりその良さがわかりませんが、CSS3の機能だとその有用さが理解できるかと思います。

例えば"multiple backgrounds"だとどうなるでしょうか。

#nice {
    background: url(background-one.png) top left repeat-x;
}
.multiplebgs #nice {
    background: url(background-one.png) top left repeat-x,
    url(background-two.png) bottom left repeat-x;
}

このCSSは、Web Designer Notebookの内容を参考にしました。表示するとこうなります。

f:id:furoshiki0223:20130629215021j:plainf:id:furoshiki0223:20130629215027j:plain

CSS側で、機能の有無を確認して、表示を切り替えることができるのです。

8. 最後に

Modernizrの解説と言いながら、クロスプラットフォームにおける相互運用性対策の思想とその実践について学ぶことになったかと思います。Modernizrは相互運用性対策に特化したツールですので、こういう説明になるのは無理もないでしょう。

HTML5を通じて、各Webブラウザベンダは相互運用性に対してシビアになり、HTML4の頃とは比べ物にならないほど改善が図られました。相互運用性は永久に解決されない課題で、小さな仕様の違いは残り続けることになるでしょう。ただ、これらをクリアしていくための技法は体系化され、進化しています。悩みを抱えた時、こうした技法について調べてみてはいかがでしょうか。

imgタグの画像のロードタイミングを制御できるjQueryプラグイン"Lazy Load"

f:id:furoshiki0223:20130628214213p:plain

Webページを表示する時、画面に表示されていない無駄な画像までロードしていませんか?

画像のロードは、Webブラウザの表示を遅らせるだけでなく、サーバ側にも負荷を与える厄介な処理でもあります。"Lazy Load"は、そんな画像ファイルのロードタイミングを、JavaScript側で制御できるjQueryプラグインです。

JavaScriptで画像ファイルのロードのタイミングを制御できるということは、画像ロードによるサーバ負荷を分散させるアプローチが可能になります。また画面に一度も表示されないような画像はロードさせないように制御すれば、負荷そのものを減らすことも可能になります。

Webブラウザ側でも、ページ自体をロードする際に一括で画像ファイルをロードさせる必要がなくなるので、表示速度の改善に繋げることができます。動作環境は、モダンブラウザはもちろんのこと、IE8以上から動くことが確認されており、かなり実用的なjQueryプラグインになっています。

▼公式サイト
http://www.appelsiini.net/projects/lazyload
▼ソースコード
https://raw.github.com/tuupola/jquery_lazyload/master/jquery.lazyload.js
▼ミニファイ版ソースコード
https://raw.github.com/tuupola/jquery_lazyload/master/jquery.lazyload.min.js |

1. まずは使ってみましょう

Lazy Loadは、img要素側とjQuery側の2箇所に仕掛けを作りこみます。

まずはimg要素。"data-original"というプロパティを追加します。このプロパティには、表示したい画像のファイルパスを指定して下さい。"src"プロパティについては、1x1ピクセルのファイルサイズの小さな画像をダミーとして指定します。

ここで透明の画像を指定すればロード中は背景色になり、灰色の画像を指定すればロード中は灰色の四角いボックスが表示されます。どんな画像ファイルでも指定して良いですが、負荷を下げる目的で利用するのであれば、1x1ピクセルのgif画像が一番妥当でしょう。

<img data-original=“img/dummy.jpg” src=“img/dummy.gif” width=“640” height=“480”>

次に、このimg要素に対して、遅延ロードさせるための機能を追加します。Lazy LoadはjQueryのプラグインなので、jQuery本体のロード後、Lazy Loadのライブラリをロードします。その後、jQueryのセレクタを利用して、遅延動作させたいimg要素に対して、メソッド"lazyload"を実行します。

<script src="./js/jquery.min.js"></script.
<script src="./js/jquery.lazyload.min.js"></script>
<script>
$("img").lazyload();
</script>

これで終わりです。

引数として何も指定していない場合は、img要素がウィンドウ内の表示範囲へ入った際に、画像ファイルのロードが開始されるという動作をします。引数へエフェクト用パラメータを指定すれば、ロード後の動作を自然な形へ変えることができます。

<script src="./js/jquery.min.js"></script>
<script src="./js/jquery.lazyload.min.js"></script>
<script>
$("img").lazyload({
    effect : 'fadeIn',
    effectspeed : 500
});
</script>

このサンプルを実行すると、画面内にimg要素が入り込んだ際に、500ms程度のフェードイン処理が施されます。これだけでも、ビジュアルは十分に良く見えるでしょう。負荷の面でも、画面内に入るまでロード処理が行われないため、クライアント・サーバ双方で低減が行えています。

2. どういう風に負荷低減されるか

Webブラウザ内で何が起こっているのかを確認してみましょう。簡単なサンプルを作成しました。一つ目は単純にimg要素を使って、12枚のpngファイルを表示するというものです。

▼サンプル1

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
  </head>
  <body>
    <h1>Sample</h1>
    <img src="img/test01.png" width="640" height="480" />
    <img src="img/test02.png" width="640" height="480" />
    ・
    ・
    ・
    <img src="img/test12.png" width="640" height="480" />
    <script src="js/jquery.min.js"></script>
  </body>
</html>

次のサンプルは、Lazy Loadを活用してイメージをロードした例です。Webブラウザ上には、一番上の一枚の画像のみが表示される状態にしてみました。

▼サンプル2

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Sample</title>
  </head>
  <body>
    <h1>Sample</h1>
    <img data-original="img/test01.png" src=“img/dummy.gif” width="640" height="480" />
  ・
  ・
  ・
    <img data-original="img/test12.png" src=“img/dummy.gif” width="640" height="480" />
    <script src="js/jquery.min.js"></script>
    <script src="js/jquery.lazyload.min.js"></script>
    <script src="js/busy1000ms.js"></script>
    <script>
$("img").lazyload({
    effect: 'fadeIn',
    effectspeed: 200
});
    </script>
  </body>
</html>

Chrome Frameを利用して、それぞれのWebブラウザの動作を確認してみましょう。まずはサンプル1,img要素で普通に画像をロードした場合です。

f:id:furoshiki0223:20130628215919p:plain

ローカル環境でテストしたということもあり、画像のロードがやたら高速になってしまいました。しかしそれでも、有益な情報は多く得られたと思います。

上から5番目の「Send Request (x13)」では、計12枚の画像とjQueryファイルをロードしています。それも一斉にです。近年は、Apacheなどのサーバサイド側の設定で、ステートフルコネクションを無効にすることで、同時処理数を向上させ最適化するのが主流です。このような同時接続はもはやあたりまえでしょう。とはいえ、Webページアクセスの初動でこんなにも多くの接続が行われるというのは、ゾッとするものがあります。

アクセス開始からペイントまで、130ms程度の時間を要しています。ただし、ペイントの後も継続して画像のロードが行われいるため、一概にこれが最短表示時間とは言えないでしょう。

次に、Lazy Loadを使った場合です。

f:id:furoshiki0223:20130628215822p:plain

80msっとそれなりに高速化されましたが、一番注目すべきは、ファイルのロード数です。計3つのファイルをロードした後、HTMLのパースを一度完了させ、その後1枚のpngファイルをロードしています。つまり計4枚だけです。

最初の3つの中には、gifファイルも含まれていますが、1x1ピクセルと非常に小さいため、負荷のうちに入っていません。そして、画面に写っていない残りのpng画像も、ロードされていません。

画像が画面に映り込んでから表示されるという動きをするので、アクセス直後に一気に負荷が集中するというWebシステムの課題も緩和されます。毎回大量のファイルをやりとりするのでなく、ジワジワと拾いに行くといった動作ができるようになるのです。また、画面に一度も表示されることの無かった画像は、ロードされないままとなり、通信量削減にも繋がっています。業務システムの場合は、画像ファイルでもそこそこ負荷になったりするケースもありますので、有効な策と言えるのではないでしょうか。

念の為に、本当にIE8でも想定通りの動きになるか確認してみましょう。IE8はタイムライン機能が無いので、開発者ツールを使って、ページの一番上を表示した際の要素の状態をそのまま吐かせます。

f:id:furoshiki0223:20130628224817p:plain

一番上のimg要素だけが、ロード済みの状態になっています。
この画像だと見難いので、ある程度横まで伸ばしたものも見てみましょう。

f:id:furoshiki0223:20130628225037p:plain

"src"プロパティが正しいpngファイルでセットされています。意図した通りに動作していますね。

3. ワンステップ上へ

ロード方法はこれだけではありません。公式ページは情報量がかなり少ないですが、他のjQuery機能を活用できるため多彩なバリエーションを持っています。性能最適化の観点で見ればこういうものも使えるでしょう。

$(function() {          
    $("img:below-the-fold").lazyload({
        event : "sporty"
    });
});
$(window).bind("load", function() { 
    var timeout = setTimeout(function() {$("img").trigger("sporty")}, 5000);
}); 

このサンプルは、5秒後にロードを開始するという動作です。先ほどは画面に見えてからロードするという動作でしたが、こちらはユーザがどこを見ているのかに関係なく、一定時間後にロードさせるという動作になります。一見微妙にも見えるこの動作、Webブラウザの仕組み、パフォーマンスの最適化の観点で見ると、結構使い道が広いように思えます。

パラメータへ100msを与えるだけでも、効果は高いように思えます。setTimeoutで指定された時間はブラウザ側に制御が戻るため、Webブラウザは余裕を持ってペイント処理を行えます。画像のロード処理をスキップした状態で、画面を表示できるのです。HTMLに埋め込まれたデータの内容が、画像のロードに邪魔されること無く素早く表示されるので、スループットの向上が期待できるでしょう。何が何でも先にデータをロードして、画像の読み込みは後回しにしたいという場合に活用できるでしょう。画像のロード自体は平行して行われるため、ロード中だからと言ってHTMLのレンダリングが抑制されることはありません。。が、影響は皆無とは言えません。場合によっては、UXを殺してしまうことになりますが。

Webシステムは今まで、単純にデザインを表示するためのスクリーンでしかありませんでした。しかしHTML5の普及やJavaScriptの高速化により、Webブラウザをアプリケーションを動かすためのアプリケーションプラットフォームとして扱えるようになり、クライアントで実装する機能も増えつつあります。そんな中、パフォーマンスに悩みを抱えることも多くなりました。

今回の画像ファイルに限らず、JavaScriptやCSSにも遅延ロードをさせるライブラリが豊富に出て来ました。余分な処理をスキップするという動作が、簡単かつ柔軟に行えるというのは、Webの新しい性能アプローチの可能性を想像させます。業務システムのように大量データを画面に吐き出させなきゃいけないようなケースで、"データ表示が最優先!"、"デザインは後回し!"、といったアプローチも、今後広まることがあるかもしれません。

ページの初期描画を高速化させる、yepnope.jsの使い方

f:id:furoshiki0223:20130627225745p:plain

yepnope.jsは、JavaScriptファイルやCSSファイルを遅延して読み込んだり、条件によって読み込む対象ファイルをスイッチさせることで、ページ応答やパフォーマンスを改善するJSライブラリです。

yepnope.jsの公式Webサイトはこちらですが、2013年6月現在はリンク切れを起こしているので、以下のURLから直接ダウンロードして下さい。

https://github.com/SlexAxton/yepnope.js

1. ページの初期表示を高速化

yepnope.jsの最もシンプルな記述は以下です。

yepnope("./js/jQuery.js");

こういう書き方もします。

yepnope({
load : "./js/jQuery.js"
});

ちなみに、CSSを読み込ませることもできます。

yepnope({
load : [ "./js/bootstrap.js", "./css/bootstrap.css" ]
});

Webブラウザは、初期ロード時に、HTMLファイル内に書かれている全ての要素を逐次実行します。このため、script/linkタグで指定されたスクリプトやスタイルプロパティも、HTMLファイル内で発見される都度、逐次コンパイル/パースされることになります。

yepnopeのような遅延ローダを利用すると、HTML読み込みから描画までの間に発生するファイルのロード処理や、コンパイル/パース処理が後回しにされるため、描画を早く行うことができます。大規模のスクリプトファイルを読み込ませる場合には、高い効果を発揮することができます。

2. 環境に合わせて読み込むファイルを最適に選択

yepnope.jsの謳い文句は、"A Conditional Loader For Your Polyfills!"です。つまり、Polyfillを行うのに適したローダと言えます。

近年はWebブラウザが多様化しているので、HTML5などの新しい機能に対応しているかを判断し、対応していない環境では、シミュレートして動作してくれるPolyfillライブラリを読み込まなくてはいけないケースが多いでしょう。

script/linkタグだと、必要・不必要関係なくロードをしてしまいますが、yepnope.jsを使うと、不要な場合にはロードしないといった対処が行えます。以下がその例です

yepnope({
    test : Modernizr.canvas,
    yep : "./css/main-canvas.css",
    nope : [ "./css/main-nocanvas.css", "./js/excanvas.js" ],
    load : "./js/jQuery.js"
});

この例では、IE8のようなCanvasが実装されていないWebブラウザでも、ある程度はCanvasの動きをシミュレートしてくれるよう、excanvas.jsと呼ばれるPolyfillライブラリを活用しています。

testプロパティには、boolean型を渡すようにします。評価式でも大丈夫ですが、例ではModernizrと呼ばれるWebブラウザの機能実装を確認するJSライブラリの評価値を引数として渡しています。Modernizr.canvasは、Webブラウザにcanvasが実装されている場合はtrueを保持します。

testへtrueが渡された時、つまりcanvasが実装されたWebブラウザである場合、yepnopeはyepプロパティで指定された"./css/main-canvas.css"をロードします。逆にcanvasが実装されいないWebブラウザの場合は、nopeで指定された"./css/main-nocanvas.css"と"./.js/excanvas.js"がロードされます。

これまでは条件付き書式などを使って無駄なロードを回避するのが定石で、もはやおまじないと化していました。canvasを利用する環境では、以下のような記述をよく目にしたのではないでしょうか。

<!--[if IE]>
    <script src="js/excanvas.js" type="text/javascript" charset="utf-8"></script>
<![endif]-->

Webブラウザを限定して機能を指定するというこの手法は、現代においてはバッドプラクティスです。

yepnope.jsにより、実装機能からロードの必要性を判断できるようになり、理想的な方法で機能をスイッチすることができます。先ほどのサンプルでも、canvasの有無に応じて、Webページのデザインを補正するCSSデザインも上手く分岐されており、実装機能に応じた機能の縮退等の対処がうまく行えています。

3. 注意点

yepnope.jsは、JavaScriptとCSS、どちらの形式のファイルもロードできます。それ故に、使い方には注意が必要です。また観点によっては、パフォーマンスは劣化しているという判断がされることがあります。これらの注意点について解説します。

3-1. CSSのFOUCへの配慮が必要

CSSファイルは、画面のデザインが遅延して反映されることにより表示が乱れるFOUCと呼ばれる現象を防ぐために、head要素内で読み込むのがお作法です。対してJavaScriptファイルは、レンダリングを出来る限り早いタイミングで完了させてからコンパイルや実行をさせるため、body要素の末端に配置するのがお作法です。

以下のイメージです。

<!DOCTYPE html>
<html>
  <head>
    ・・・
    <link rel="stylesheet" href="css/bootstrap.min.css" />
  </head>
  <body>
    ・・・
    <script src="js/jquery-1.9.1.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

yepnope.jsを使ってロードするとどういう動きになるでしょうか。サンプルと同じく、bootstrapとjQueryをロードさせてみましょう。

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
  </head>
  <body>
    <div>dummy</div>
    <script src="./js/yepnope.1.5.4-min.js"></script>
    <script>
yepnope([
    "js/jquery-1.9.1.min.js",
    "js/bootstrap.min.js",
    "css/bootstrap.min.css"
]);
    </script>
  </body>
</html>

このサンプルは実装の観点から見て、どのような形でロードされるのでしょうか。chromeを使って要素を検証してみましょう。

f:id:furoshiki0223:20130628001910p:plain

yepnopeで指定されたファイルは、最終的にはJavaScriptやCSSをロードするための要素に置き換えられ挿入されました。しかしパッと見た印象としては、とても残念な結果に見えます。CSSがbodyの末端へ挿入されているのです。

これをなんとか改善できないでしょうか?head要素上でyepnopeを実行してみましょう。

f:id:furoshiki0223:20130628002405p:plain

全てヘッダへ詰め込まれました。今度はJavaScriptファイルがヘッダに挿入されてしまってます。一見すると、CSSの問題についてはこれで解決されているように見えますが、問題の根本は何も解決されていません。

以下のコードを実行してみましょう。

▼index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
    <script src="./js/yepnope.1.5.4-min.js"></script>
    <script>
yepnope("css/user.css");
    </script>
  </head>
  <body>
    <div class="dummy">dummy</div>
    <script src="js/load1000ms.js"></script>
  </body>
</html>

▼css/user.css

.dummy {
    color : red;
}

▼js/load1000ms.js

(function() {
    var begin = (new Date()).getTime();
    while ( ((new Date()).getTime() - begin) < 1000 )  {
    }
})();

"css/user.css"内には、dummyの文字色は赤という指定がされています。"js/load1000ms.js"は、負荷が高い処理を再現するために作成したもので、1000ms(1秒間)のビジーウェイトが発生します。"index.html"からは、scriptタグを使って"js/load1000ms.js"を直でロードしつつ、yepnope.jsを用いて"./css/user.css"をロードしています。

このコードを実行すると、画面にdummyの文字が黒く表示され、1秒後に赤い文字に変化します。これはIEでもChromeでもFirefoxでも同様です。どのような環境でも、FOUCが発生することになります。

yepnopeで指定した"css/user.css"は、"js/load1000ms.js"により発生した、ビジーウェイトの後で実行されいます。CSSが重い処理を行うJSよりも後に読み込まれたため、デザインが反映される前の状態が画面に表示されてしまったのです。HTMLファイルでの見かけ上は、head要素にlink要素が埋め込まれているため、CSSの方が早くロードされているように見えますが、実体としては全く別の動きをしていることになります。

yepnope.jsは恐らく、body要素の末端で実行することが主になるでしょう。polyfillを必要とするシーンや、パフォーマンス向上が求められるシーンで活用されることになるかと思います。しかし、全てのファイルを遅延実行させることに固執してはいけません。CSSはFOUCが発生しユーザーエクスペリエンスを低下させることになるため、必要最低限に留めるべきでしょう。多くの場合、JavaScriptのロードの最適化に役立てることができる技と捉えるべきです。

3-2. 常にパフォーマンス向上が見込めるわけではない

yepnopeによるロードは、HTMLファイルの内容を読み込み、一旦表示が完了してから実行されるというイメージなのは理解できたかと思います。実際にどのような手順でロードを行なっているか、chromeのタイムライン機能を使って確認してみましょう。

まずは、yepnopeを利用しないでロードした時です。bootstrapとjQueryを利用したページの例です。実行の遅延を再現するため、実行に100msを要する"load100ms.js"と、200msを要する"load200ms.js"を作ってみました。

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
    <link rel="stylesheet" href="css/bootstrap.min.css" />
  </head>
  <body>
    <script src="js/load100ms.js"></script>
    <script src="js/load200ms.js"></script>
    <script src="js/jquery-1.9.1.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

f:id:furoshiki0223:20130628012128p:plain

ロードしてからペイントまでに、合計約350ms程度かかりました。ペイントとは、Webブラウザのウィンドウ内に描画を行う処理のことです。今度はこれを、yepnopeを使って表示してみます。

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
    <link rel="stylesheet" href="css/bootstrap.min.css" />
  </head>
  <body>
  	<script src="js/yepnope.1.5.4-min.js"></script>
  	<script>
yepnope([
    "js/load100ms.js",
    "js/load200ms.js",
    "js/jquery-1.9.1.min.js",
    "js/bootstrap.min.js",
]);
    </script>
  </body>
</html>

f:id:furoshiki0223:20130628012741p:plain

今回は、ペイントが計2回走りました。

一回目は60msでペイント。前回が350msだったので、非常に高速に動作したように思えます。しかし、2回目は開始後から450ms弱程度でペイントされました。トータルで見れば遅くなっています。

遅延ローダはよく"高速だ!"、"パフォーマンスが上がる!"と言われますが、観点次第では遅くなったとも判断できます。そもそも、ローダ自身を読み込み実行する手間が増えるわけですから、トータルが長くなるのは当然なのです。

yepnope.jsなどの遅延ローダが高速と言われるのは、ペイント処理のタイミングが原因です。ページアクセスを開始してから描画されるまでの時間が短いほど、ユーザエクスペリエンスは向上します。

遅延ローダはパフォーマンスを上げるのでなく、体感速度を上げるものです。ユーザを騙しているわけです。より具体的に言うなら、画面ロード直後に起こる重い処理をスキップさせ、ペイント処理を先に発生させているわけです。スキップしているわけですから、そのツケはどこかで払う必要があります。

この微妙なギャップは、業務システムの場合、TAT(ターンアラウンドタイム)の基準によって明確な違いとして現れます。SLA契約の内容が"画面を表示するまで"か"データを出力しきるまで"かで、アプローチが違ってくるのです。

業務システムだと後者を妥当と考える人も多いでしょう。その場合は、遅延実行はあまり有効な策とは言えません。ただ、ユーザエクスペリエンスで見れば、前者の"画面を表示するまで"が重要になるはずです。UXは最近しきりに叫ばれ、業務システムにもその波が押し寄せてきていますが、時と場合によっては、こうしたジレンマがついて回ります。

polyfillの場合は、条件によってファイルのロードを行わないといった用途で活用するでしょう。ファイル数が減るのだから早いのは当然!と思うのは早計です。yepnope.jsは非常に小さなライブラリですが、読み込みとコンパイルが必要なことに変わりはありません。yepnope.jsロードによるオーバヘッド、顧客との非機能要求に対する契約内容(SLA)、両者に正確な認識があってこそ、初めて十分な効果を期待できるでしょう。

4. yepnope.jsの機能仕様

yepnopeは非常にシンプルなライブラリで、仕様はこれだけです。

yepnope([{
  test : /* boolean(ish) - 条件式          */,
  yep  : /* array (of strings) | string */,
  nope : /* array (of strings) | string  */,
  both : /* array (of strings) | string  */,
  load : /* array (of strings) | string   */,
  callback : /* function ( testResult, key ) | object { key : fn } */,
  complete : /* function */
}, ... ]);
  • test : 条件式
  • yep : testがtrueだった場合のみロードするファイル
  • nope : testがfalseだった場合のみロードするファイル
  • both : testがtrue/false関係なくロードされるファイル
  • load : bothと同様の動作をする
  • callback : 個々のファイルのロードが完了した際にコールバックされる関数
  • complete : 全てが完了した際にコールバックされる関数

JavaScriptライブラリなので、この処理をbodyの子要素のうち、一番後ろに配置するのが理想です。

5. 最後に

polyfillの思想の部分と、実態のパフォーマンスの部分、双方からyepnope.jsを評価してみました。この記事を通じて、だいたいどんなイメージで使えば良いかは理解できたかと思います。CSSのFOUCにせよ、遅延実行のオーバヘッドにせよ、何にでもデメリットがあるので、やり過ぎは良くなく、ライブラリが意図する方法で使うのがベストと言えます。

Polyfillライブラリの読み込みに強いうのを強くアピールしてきましたが、Modernizrのダウンロードページにもチェックボックス一つでyepnope.jsを追加できるようなオプション機能が付いていたりしてて、Modernizr側もセットで活用する価値が高いことを正式に認めているわけです。

yapnope.jsは単体でも有用で、単純な遅延ローダとして使うのも良いでしょう。ただ、せっかくyepnope.jsを選択して使うのわけですから、Modernizrへ組み込んで利用してしまうのが良いでしょう。既に様々な用途で使われているようですが、yapnope.jsの良さを十分に発揮させるのは、Polyfillだと私は思っています。

CSSのロードタイミングの不備が引き起こす、FOUCとは?

FOUC(Flash of Unstyled Content)とは、Webページへアクセスした直後、CSSによるデザインが有効でないページが一瞬だけ表示される現象のことです。

まず、下の例をご覧下さい。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf8" />
    <title>Example</title>
    <link rel="stylesheet" href="hoge.css" type="text/css" />
  </head>
  <body>
    ・・・
  </body>
</html>


この例では、CSSの外部ファイルをhead要素内から呼び出しています。この方法はもうずっと昔から守られている伝統で、誰しもが無意識のうちにやっていることでしょう。

しかしCSSの外部ファイル参照は、head要素の外でやっても問題なく動作します。つまり、以下のような書き方も許容されるわけです。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf8" />
    <title>Example</title>
  </head>
  <body>
    ・・・
    <link rel="stylesheet" href="hoge.css" type="text/css" />
  </body>
</html>

この例では、bodyタグの一番後ろの要素にCSSファイルの参照を行わせています。もちろんこれでも動作はしますが、問題を孕んでいます。

Webブラウザはページをロードする際、ロードしたページの内容、つまりHTMLファイルを上から順に読み込み処理します。html要素を読み込み、head要素を読み込み、body要素を読み込み、body内のdivやul要素を読み込み・・・、と順番にDOMを解釈してはレンダリングを行うといった動作をするのです。

これはScriptやStyle要素でも同様で、script要素を見つけたらscript要素内のJavaScriptをコンパイルし実行、style要素を見つけたらStyle要素内のCSSをパースしプロパティをロード、これは外部ファイルであっても同じ動きになります。HTMLファイル内を構成する各要素は、例えスクリプトであってもデザインの指定であっても、直列に解釈され処理されていくことになります。

するとどうなるでしょう。2番目に挙げた例では、CSSのロードをbody要素の最後で行っていました。body要素内の全てのDOMツリーのロードが終わり、せっかくレンダリングが完了しているのに、最後の最後というタイミングでCSSのプロパティが読み込まれてしまいます。せっかくレンダリングしたのに、CSSが最後に読み込まれてしまったことで、変更後の装飾された状態のDOMを再び計算しレンダリングすることになるのです。

もし参照されているCSSファイルのロードが遅い場合、或いは直前のJavaScriptで重い処理を行った場合、ブラウザ上には一瞬だけ、CSSで装飾される前の状態が表示されることになります。これがFOUCと呼ばれる現象です。

かつてはWebブラウザの作りの悪さを揶揄する言葉でした。IE5は如何なる時でも、このような動作を行っていたためです。しかし改善が進んだ今、この問題は、Webブラウザの作りでなくWebコンテンツの作りによって引き起こされます。

作りによっては、FOUCをどうしても避けられないケースもあります。ただそれは本当に限られたケーススタディのはずです。基本的にはCSSの読み込みをhead要素内で行うようにし、ユーザエクスペリエンスの低下を最小限に抑えるよう努めましょう。

このブログの筆者について

川田 寛

コンテンツサービスの開発や運営代行を専門とする集団「株式会社ブートストラップ」の社長です。ネットではふろしきと呼ばれています。

2009年にNTTグループへ新卒入社し、ITエンジニアとしてクラウド技術・Web技術の研究開発と技術コンサルティングに従事。2015年よりピクシブに入社し、エンジニアリングマネージャー・事業責任者・執行役員CCOなど、様々な立場からコンテンツサービスの事業づくりに関わりました。2021年にメディアドゥへVPoEとしてジョインし出版関係の事業に関わったのち、2023年に独立しています。

関わってきたインターネット事業としては、ECサービスのBOOTH、UGCプラットフォームのpixiv(主に海外展開)、制作ツールのpixiv Sketch、VR・VTuber関連ではVRoid、Wikiサービスのピクシブ百科事典など、10を超える多様なCtoCコンテンツサービス。また、NTTドコモのすご得コンテンツ、メディアドゥのWeb3サービスであるFanTopなど、いくつかのBtoCコンテンツサービスにも関わってきました。

幸運なことに、私はコンテンツに関係する幅広いインターネットサービスのテクノロジー&ビジネスの知識を得ることができました。これを日本のコンテンツ発展に役立てたいと思い、株式会社ブートストラップを創業しました。

このブログでは現在、出版社やIPホルダー、ライセンサーといったコンテンツに関わる人々に向けて、インターネット事業に関するTipsや業界内のトレンドなどの情報を発信しています。私と話をしてみたいという方は、以下のフォームより気軽にご連絡ください。

お問い合わせフォーム