2016年5月18日〜20日、Googleさんの本社Googleplexの横にあるShoreline Amphitheatre(ショアライン アンフィシアター)で行われたGoogle I/0 2016のセッション「What’s new for the web?」についてのレポート前編です。前編では、Webの最先端として、既に導入済みの機能やAPI、またこれから導入される機能を怒涛のごとく紹介します!
今現在Webで起こっていること
Progressive Web App (goo.gl/WR7yJ3)
Service Worker、Add to Home Screen、App Manifest、Background Sync等を使い、NativeアプリのようなUXを提供するWebアプリを指す言葉です。 Chris氏いわく、23年間Webをやってきているけど、やっと理想だと思っていた世界、ユーザーとのエンゲージ率が高いUXを持つサイトがWebというPlatformのみで制作することが可能になっている。(作り方等の詳細はこちら「The Mobile Web: State of the Union」でとのこと)
けど、ここで話すのは既に利用可能なCutting Edgeな話ではなくて、「New Shiny」と呼んでるWeb PlatformのBleeding Edgeな話をします。なので、まだリリースされていない機能だったりCross-Browser、Cross-Deviceで動かない機能、Experimentalフラグの変更を必要とする機能も含まれます。
ただここではカバーしない機能もあり、その代表的なものの一覧はこちらです。
New Shiny(Bleeding Edge)
開発をより簡単に速くするための機能(導入済み)
Promise
非同期のAPIの記述を一貫性のあるパターンで書く方法です。Callbackのように混乱しやすかったり、散らかりやすくなく、同期APIをブロックすることもなくなります。Web開発はブロッキングをなくすようなデザインをするように考え方を変える必要があります。なのでPromiseは新たなWebのAPIをデザインするときには一般的な方法になっていて、以下はPromiseを使うAPIとしてデザインされている代表的なものです。
- HTMLMediaElement.play():Promiseに書き換えられました
- Web Bluetooth
- Web USB
- Web MIDI
Fetch
FetchもPromiseを使っています。FetchはXMLHTTPRequest(XHR)と似ていてNetworkリクエストを行います。XHRとの違いはPromiseを使って可読性が上がっているところ。Fetchで何をしているか分からなくても、見たら何となく内容を理解できるところです。Callback地獄からの脱却、XHRの複雑な部分から開放してくれる。例えば、直接JSONや他のデータ・タイプを扱うことも可能です。
左がFetch、右がXHR。パッと見、XHRとも悪くないけど、Stateのチェックとかしてないから悪くないのであって、本来はもっと長くゴチャゴチャするのでご注意してください。
そしてES6のアロー・ファンクションを使うと、さらにこんなにもキレイになります。
ただ、さらなるチャレンジ。問題はこのままだとデータを完全に取得してからでないとJSONとしてParseができないところ。つまりAtomicで割り込みができない。そこで実装中なのが、このFetchやその他のAPIの本当の能力を引き出す機能、それがStreamです。
[実装状況]
Stream
Streamは基本的な要素で、その使い道はいろいろあると思います。Jakeはこんなことを言ってます。「Streamは2016年の大きな1つの機能になるだろう」と。なぜならStreamはProgressiveなデータ転送を可能にするからです。全てのデータを受け取っていない状態でも、受け取った順に処理することができる。つまり、Progressiveなレンダリングだったり処理が可能になるのです。App Shellアーキテクチャを使う場合等は1度に複数の場所からデータを取得するので、とても重要な要素になります。
画像は左側の「Fetch」の全てが終わらないと真ん中の「Process」に進めない、それに引きづられて最終的な「Render」も遅くなってしまう、を表した図。
Jakeが作成したOffline Wikipediaのサイトで、Streamを使うとどれくらい差がでるかを比較したデモ動画です。左からServer Render、Service Worker Client Render、Service Worker Client+Hacks Render、一番右がStreamを使ったService Worker Client Renerです。(画像が小さくてすみません。セッションでのビデオ再生はココから見られます)
初期のレンダリングは一番左のServer Renderが他よりも8倍くらい遅い、しかしレンダリング完了までだとnon Streamの真ん中の2つよりも速い。この理由は、真ん中の2つは全てのデータを取得するのを待ち、それから処理を始め、その後最終的にレンダリングされるからです。せっかくのハイパフォーマンスなProgressiveなWebアプリなサイトであるのに、表示が遅くては致命的とも言えるでしょう。実際に、現況ではApp Shellアーキテクチャのがコストが高い(時間がかかる)場合もあります。
[実装状況]
さらにパフォーマンスをテーマに話を続けます。ハイパフォーマンスなWebサイトを作るために実装しているエキサイティングなその他の機能を紹介します。
RequestIdleCallback
多くのWebアプリ、サイト上では多くのスクリプトが動いていることと思います。しかし全てのスクリプトがユーザーが見ている画面に直接関係のあるスクリプトではなかったりしますよね。例えば、Analyticsデータの送信です。スクロール中に動いてしまうとスクロールがロックしたりユーザー体験(UX)の質は下がります。そこで、そういったユーザーが見ている画面に直接影響を及ぼさない処理を空いている時間に走らせよう、という機能がRequestIdleCallbackです。
RequestAnimationFrameはできるだけ60fpsで表示ができるようその処理をスケジュールしています。そして、RequestIdleCallbackはRequestAnimationFrameと一緒に動作しています。レンダリングに関係しない処理を、レンダリング処理が空いているところにスケジュールしてくれる、という動きです。
実装は難しくないけど、ここで動かしたいタスク(処理)の1つ1つはリーズナブルな時間で終わるように分割されている必要があります。時間がかかるタスクは今まで通りWeb Workerで動作させることをおすすめします。ちなみにNetflex、Facebookはこの機能を既に導入しています。
[実装状況]
PassiveEventListener
新しいDOMの機能でChrome 51でShipされます(I/O後の2026年5月下旬にリリースされました)が、ユーザーによりよいスクロール機能を提供するための機能です。導入はすごく簡単。
スムーズなスクロールの機能はWebにとって、特にタッチデバイスでのWebにとってはとても重要です。モダンなブラウザは高コストなJavaScriptがUIスレッドで走っていたとしても、スクロールを別スレッドでハンドリングする機能を持っています。
なのですが、問題はそれがタッチやスクロールeventハンドラを持っていると、最適化で負けてしまうところです。なぜならそれらのハンドラは.preventDefaultをCallすることでスクロールを完全にブロックしてしまうからです。eventハンドリング中に.preventDefaultがCallされるかどうかは分からないので、高コストなJavaScriptの処理が完了するまで待つ必要が出てきてしまうのです。
そこでPassiveEventListenerでは、.preventDefaultをCallしないと宣言し、スクロールがeventによってブロックされないようにするのです。
Chrome for Androidではその80%のTouchEventがブロックされる必要はないのにブロックされている。その10%がスクロール開始までに最低100ms待ち時間が生じた。さらに、その1%は500msの待ち時間がスクロール開始までに生じている。
CNNのサイトで導入したのと、していないのとで比較をしてみます。(ビデオはこちらから:preventdefaultをcallしてないのに遅くなっているのとPassiveEventListenerを使った場合の比較ビデオ)
この違いを生むための実装は、たったこれだけです。
Intersection Observer
特定のDOM Elementが画面内に入っているかどうかを見る、またその場所も簡単に得ることができるのがこのAPI。今までも実装は可能だったけど、効率的ではなかったし問題もいろいろあった。では、Intersection Observerのコードを見てみましょう。
画面内に入ったかを監視したいターゲットとなるElementを決めましょう。そしてIntersectionObserverのオブジェクトの生成時します。生成時には画面内に入った場合の動作についてのCallbackも渡します。最後に生成したオブジェクトでどのElementを監視するかを指定します。(デモページ:goo.gl/mNdYRv、Chrome 51以降で御覧ください)
デモページはとてもスムーズで超速な無限のスクロールを提供しています。コンテンツ表示はネットワークの遅さをシュミレートしているかのように遅れていますが、Service Workerを使ってキャッシュすると解決します。例えば、たくさんの画像を表示するページ等には持ってこいですね。
[実装状況]
<link rel=”preload”>
Chrome 50でShip済みで、リソースのPreload(事前読み込み)を行います。遷移先のページを事前取得する<link rel=”prefetch”>とは違い、現在のページ内のリソースを読み込む優先順位を指定ができる属性です。
[実装状況]
HTTP Clients Hints
HTTPヘッダにつけることで取得するコンテンツの種類を事前ネゴシエーションすることができる。ネゴシエーションの要求を出すことで、ユーザーのデバイス、環境によってサーバ側が提供されるコンテンツを差替えてServeすることが可能です。サーバ側に実装されるものであって、クライアント側のコードで指定するものではありませんが、以下の様な要求ができるように用意されています。
- DevicePixelRatio(DPR)
- Preferred widt
- viewport-width
- save-data: サーバにデータ転送を節約していることを知らせることができる
Canvas.toBlob
Chrome 50でShip済みで、クライアント側でCanvasをそのままBlobにすることができます。一説によるとこの機能が実現するまでに6年かかったそうです。.toDataURL()から得られるBase64の文字列を操作することなく、画像としてそのままサーバにアップロードしたり、IndexDBに保存したりできるので、より簡単に再利用することが可能になります。
[実装状況]
ImageBitmap
Chrome50でShip済みで、ImageBlobを他のCanvasにそのまま描くことも可能になっています。Canvasに描くために画像をデコードするのはとても一般的ですが、画像のデコードにCPUリソースをとても多く使ってしまうという問題点があります。しかし、Base64とデータの間での処理をすることなく扱えるようになったのです。そうすることで、例えば他の画像、エレメント、ビデオ等をCanvasに描くときと同じように扱えるようになりました。
[実装状況]
Unified Media Pipeline
クライアントではなくてChrome自体のコードの話です。デスクトップ版でもモバイル版でも全く同じ処理がされるようなMediaようにPipelineを持つようにしました。Chrome for AndroidにおいてもOSが持つAndroid Media Stackでの処理は行わないようにしました。これによりCross-Platformでも一貫した処理(例えばキャッシュ、エンコード、デコード)を行うようになっています。よって、Cross-Platformでのデバッグをより簡単にしています。
MediaRecorder
ビデオ、オーディオをキャプチャするだけでなく、エンコードして保存をすることが可能になりました。例えば、取得したオーディオをMP3にクライアント側で変換してサーバにアップロードすることが可能です。 (デモページ:simpl.info/mediarecorder、Chrome 49以降でご覧ください)
コードはこんな感じです。MediaRecorderのオブジェクトをOptionを渡して作成して、データを取得したときのCallbackを.ondataavailableに指定します。Callbackにはどう扱うかを書くのだけど、ほとんどの場合取得したデータ(Blob)をくっつけるだけです。そして、最後にどれだけの長さの動画を作るかを指定して完了です。サーバにアップロードする、ローカルに保存する等ができるようになります。Web Audioでの質問として何度か受けていますが、まさにこれがその解です。
[実装状況]
Media Session
Mediaの動作を操作のカスタマイズ、例えばラップトップのオーディオように用意されたキーを特定の機能に割り当てることができたり、モバイルでは通知エリア(Notification Area)、ロック画面にMedia操作用のUIを出すことができるAPI。もしMediaに関するアプリを作っている開発者であれば一度見てもらいたい。
[実装状況]
CSS Variables
正確にはCSS Custom Propertyとも言いますがChrome 49でShipされています。例えば、同じ属性を何度も繰り返し書くことの削減してくれたり、また色・見た目等のテーマの変更にもとても有効です。
[実装状況]
CSS Containment
CSSを隔離してパフォーマンスを最適化してくれる機能です。例えば、3rd Partyのウィジットを使った場合、そのウィジットのCSSを隔離することで自サイトのパフォーマンスに影響が出ないように最適化してくれます。
[実装状況]
CSS Font Loading API
最初に実装されたのはChrome 35でそこそこ前なんですが、Font LoadingのInterfaceはChrome 48でShipされたばかりです。1つ1つのFont Faceのチェック、またそれらのダウンロード状況を確認できるようになりました。これによりFontが実際にどれくらいダウンロードされたか、どんなFontであるかを知ることができるようになりました。
[実装状況]
Progressiveに速いスピードで短い説明で新しい機能を紹介しまくって、後で調べてね、と言っている感があるように聞こえるかもしれないですので、少し休憩というようなお話をしますね。
(後編の予告など)
ということで、この辺りで前編は終了です。後編はWeb Platformをより先に進めるためにここ1~2年Chromeチームが取り組んでいるアプローチ、Chris氏の大好きなアレ、そしてWeb Bluetoothについてお伝えします。