UI のレスポンスを向上させる Web Storage の使い方 #html5j

HTML5 Advent Calendar16 15日目を担当させていただく ogaoga です。 今年のはじめ頃に、html5j.org 主催の「第0回 HTML5 プログラミング&クリエイティブ・コンテスト」が開催され、応募した「icotile」が自由課題部門で優秀作品賞を頂きました。 そのお礼もかねて、icotile で活用した HTML5 の機能の一つである「Web Storage」について書きたいと思います。なお、Web Storage の詳細については言及しませんので、参考文献をご覧頂ければと思います。また、文中のソースコードは擬似的なもので、初期化やエラー処理などは省略しています。

icotile と Web Storage

icotile は、Twitter のフォロワーを iTunes のような UI で管理するための Web アプリケーションです。画面中央部にフォロワーのアイコンをすべて並べて、選択するとそのユーザーの詳細情報が表示されたり、ドラッグ&ドロップでリスト編集ができます。(Twitter を使っている方はぜひお試しください。)

icotile main view

icotile のメイン画面

今回 icotile の開発では「デスクトップアプリケーションのような操作感」を目指しました。そのためには、ユーザーの操作に対して素早く反応することが重要です。これまでの Web アプリケーションの様に、操作の度にサーバにデータを取りにいくようだと、どうしてもユーザーを待たせてしまいます。 このような場合にはよく「キャッシュ」という手法を使うのですが、HTML5 以前にはブラウザ側でキャッシュするための保存領域がありませんでした。HTML5 になってからは、ブラウザにデータを保存する仕組みがいくつかあり、icotile では「Web Storage」という仕組みを使って、ユーザーの情報をキャッシュしています。これにより、起動後すぐにアイコンが表示されたり、アイコンをクリックしてすぐに詳細情報が表示されたりと、レスポンスの早い UI を実現しています。 ちなみに、Web Storage の他にも Web SQL DatabaseIndexed Databaseという技術もありますが、手軽に実装でき、幅広いブラウザに対応している Web Storage を採用しました。

Web Storage の使い方

Web Storage は key と value という組み合わせを1レコードとするストレージで、下記のような JavaScript のコードでデータの保存、取得が可能です。

// 保存と取得
// この例では、twitterUserId が key で、userDataJson が value

// 保存
localStorage.setItem(twitterUserId, userDataJson);
// 取得
var userDataJson = localStorage.getItem(twitterUserId);

icotile では、Twitter の API で取得できるユーザーの情報を localStorage にキャッシュとして格納しています(Web Storage には2種類のストレージがあり、localStorage は永続的にデータを保存できる領域です)。 key にはユーザーを一意に示すユーザーの id、value には Twitter API で取得したユーザーの情報を、JSON 形式そのままで格納しています。 ユーザーの情報を利用する際、最初に localStorage を参照して、データが存在していたときにはそのデータを利用します。初回起動時など、localStorage にデータがない場合にはサーバからユーザーの情報を取得します。取得したデータは localStorage に保存します。いわゆる一般的なキャッシュの仕組みと同じです。 icotile では、ユーザーがアイコンをクリックすると、そのアイコンのユーザーの詳細情報が右側に表示されますが、このときに下記のような処理を行っています。

// 最初にキャッシュ(localStorage)にアクセス
var userDataJson = localStorage.getItem(twitterUserId);

// JSON をオブジェクトに変換
var userData = JSON.parse(userDataJson);

if ( userData ) {
  // データが存在しているので、詳細を表示。
  drawUserDetail(userData);
}
else {
  // データが存在していなかったので、サーバから取得
  // 第2引数は、取得後に呼び出されるコールバック関数
  requestUserDataFromTwitter(twitterUserId, function(userDataJson) {
    // JSON をオブジェクトに変換
    var userData = JSON.parse(userDataJson);

    // サーバから取得したデータを表示。
    drawUserDetail(userData);

    // キャッシュに保存
    localStorage.setItem(userData.id, userDataJson);
  });
}

容量の上限

Web Storage は記録できる容量に上限があります。ブラウザの実装にもよりますが、Chrome の場合、ドメインあたり 5MB です。Twitter のユーザーのデータ(JSON)は、人にもよりますがだいたい 2KB 程度なので、2500 人分ぐらいは localStorage に保存することが可能です。データにはアイコン画像そのものは含まれていないので、アイコンの表示のスピードはブラウザのキャッシュに依存します。 キャッシュが上限に達した場合には、localStorage.setItem() 実行時に例外が発生します。この場合には、新しい情報を追加できるように古い情報を削除します。icotile では、上限に達した場合には 100件削除した後に保存しています。本来なら保存した日時を記録して古い順から削除するのがよいのですが、Web Storage は日時情報を保存したり、その値でソートすることが出来ないので、単純に取得した 100件を削除しています。

try {
  // データの保存。上限に達している場合には例外が発生する。
  localStorage.setItem(twitterUserId, userDataJson);
} catch (e) {
  if ( e == QUOTA_EXCEEDED_ERR ) { // 上限に達していた場合
    var i = 0;
    for ( var key in localStorage ) {
      // データの削除
      localStorage.removeItem(key);
      if ( i++ > 100 ) {
        break;
      }
    }
    // データの保存
    localStorage.setItem(twitterUserId, userDataJson);
  }
}

プライバシーの考慮

localStorage のデータは明示的に削除しない限り保存されたままになるため、ブラウザを終了して再度アクセスしたときにも、残っているデータは素早く表示することが出来ます。そのため、プライバシー保護の観点でキャッシュを全クリアする機能をユーザー提供した方がよいでしょう。また icotile ではログイン時にユーザーをチェックして、違うユーザーでログインした場合にはキャッシュをクリアしています。データが永続的に残るので、うっかりアクセスできてしまうことも考慮に入れる必要があります。 また、localStorage の中身はブラウザの機能で見ることが可能です。開発時はデバッグ等で使えますが、ユーザーが除き見ることも出来るため、パスワードのような情報を保存することは避けた方が無難です。

localStorage の中身(Chrome)

Chrome のローカルストレージのブラウザ

応答性能と情報の鮮度

このように、Web Storage を使ってデータをキャッシュして、ユーザーの操作に対して素早く情報を提供することが出来ますが、キャッシュをするということは情報の鮮度が劣化している可能性があります。ユーザーのプロフィールのように頻繁に更新されない場合にはあまり影響はありませんが、リアルタイムに更新される情報を扱う場合にはキャッシュしない、もしくは定期的に更新したほうがよいかもしれません。応答性能と情報の鮮度のバランスをどう取るかは、ユーザビリティ上重要で、かつ難しいポイントかと思います。この辺りは、実際にコーディングしていいバランスポイントを見つけ出すしかないかなと思います。


と、簡単ですが Web Storage の使い方と考慮のポイントをまとめてみました。「データベース」というとちょっと身構えてしまいますが、キーバリュー型で簡単に扱うことができるため、いろいろ場面で活用することが出来ると思います(ちょっと容量が少ないですが…)。ぜひお試しくださいませ。

カテゴリー: HTML5, icotile タグ: パーマリンク  

コメントは停止中です。