fc2ブログ

[HTML5]ダウンロードデータのキャッシュとしてWeb Storageを使う

囲碁定石トレーニングを、「ダウンロードした定石データをWeb Storageに格納する」ように修正した。

目次
1. localStorageかsessionStorageか
2. Web Storageを使用可能かチェック
3. Web Storageにデータが存在するかチェック
4. Web Storageにデータを登録する

1. localStorageかsessionStorageか
Web StorageにはlocalStorageとsessionStorageがある。
有効期限が無いlocalStorageの方が、無駄なダウンロードをより削減することができる。
ただし、定石データの変更があった場合にデータの同期を取る必要がある。
今はそこまでしてダウンロードを減らす必要が無いので、タブを開いているときだけ有効なsessionStorageを使うことにした。
Web Storageを使用できない環境では、今まで通りキャッシュ用オブジェクトに格納するようにした。

2. Web Storageを使用可能かチェック
コードは以下のようになった。
  mijoseki.act.isStorage = false;
  try {
    if (typeof sessionStorage != 'undefined' && typeof JSON != 'undefined') {
      sessionStorage.setItem('test000', 'check if storage is available');
      mijoseki.act.isStorage = true;
    }
  } catch(e) {
    mijoseki.act.isStorage = false;
  }
Web Storageが使用可能ならmijoseki.act.isStorageフラグをオンにするだけの処理。
cookieをブロックしていたりすると例外が発生するので、try catchで例外対応をしている。(詳細は[HTML5]Web Storageについて調べるを参照)
例外を発生させるためにダミーデータをsetItemしている。
JSONオブジェクトは、オブジェクト⇔文字列 の変換に使用するためチェックしている。

3. Web Storageにデータが存在するかチェック
コードは以下のようになった。
  /**
   * 問題データキャッシュチェック
   * @param {string} probkey 問題のkey値
   * @return {Object} this.setprob() でセットする問題データ。
   * 該当するキャッシュが無い場合はnull。
   */
  mijoseki.Action_.prototype.chkpCache = function(probkey) {
    var pdata = null;
    if (this.isStorage) {
      pdata = sessionStorage.getItem(probkey);
      if (pdata) {
        pdata = JSON.parse(pdata);
      }
    } else{
      // sessionStorageが使用できない環境での処理
      if (this.pdatacache[probkey]) {
        pdata = this.pdatacache[probkey];
      }
    }
    return pdata;
  };
キー値に該当するデータがあったら、JSON.parse()でオブジェクトに変換してreturnするだけ。

4. Web Storageにデータを登録する
コードは以下のようになった。
  /**
   * 問題データキャッシュセット
   * @param {string} probkey 問題のkey値
   * @param {Object} pdata セットする問題データ
   */
  mijoseki.Action_.prototype.setpCache = function(probkey, pdata) {
    if (this.isStorage) {
      var setdata = JSON.stringify(pdata);
      try {
        sessionStorage.setItem(probkey, setdata);
      } catch(e) {
        // 保存容量オーバーなどでStorage使用不可の場合
        mijoseki.act.isStorage = false;
      }
    } else{
      // sessionStorageが使用できない環境での処理
      if (!this.pdatacache[probkey]) {
        if (this.pdatacname.length >= mijoseki.con.num.climit) {
         // キャッシュ数が上限のため、古いキャッシュを削除
         var delval = this.pdatacname.shift();
         delete this.pdatacache[delval];
        }
        this.pdatacname.push(probkey);
      }
      // pdataをshallow copyしてキャッシュに格納
      var pdatacp = {};
      for (var prop in pdata) {
        pdatacp[prop] = pdata[prop];
      }
      this.pdatacache[probkey] = pdatacp;
    }
  };
Web Storageには(今現在では)オブジェクトを登録できないので、JSON.stringify()で文字列に変換している。
setItemで例外が発生した場合は、キャッシュを登録せずにフラグをオフにする。次にダウンロードしたときに、キャッシュオブジェクトに登録される。
キャッシュオブジェクトでは上限を設定しているが、今の定石データは合計25KB以下なのでsessionStorageでは上限チェックをしていない。

以上。難しいことは何も無かった。
各ブラウザでcookieをブロックしたり許可したりしてテストするのが面倒だったぐらい。
スポンサーサイト



[HTML5]Web Storageについて調べる

cookieよりも大容量のデータを保存できるWeb Storageについてちょっと調べた。

概要から使い方まで、たいていのことは以下の記事を読めばわかる。
ブラウザでストレージ? Web Storageを使いこなそう - @IT

Web Storage仕様書の日本語訳は
W3C - 『Web Storage』日本語訳 - HTML5.JP
にある。

Web Storageを使うにあたっての注意点は以下のとおり。
1. cookieをブロックしている場合、例外が発生する。
cookieをブロックしているときの各ブラウザの挙動は以下のようになった。
・Firefox7.0 : 'security error'発生。
・Google Chrome15 : 'QUOTA_EXCEEDED_ERR'発生。
・Internet Explorer8 : 例外が発生せず、Storageに登録可能
Firefox, Google Chromeともに、Storageのメソッドを使用するところで例外が発生するが、
Firefoxでは
if(typeof localStorage == 'undefined')
でも例外が発生した。
なお、
if(typeof sessionStorage == 'undefined')
では発生しなかった。

try catch を使用して例外発生時の対応を記述する必要あり。
IEでも容量オーバーの場合は例外が発生するので、例外対応の必要あり。

2. localStorage は、「ドメイン:ポート番号」単位で管理される。
同一ドメインであれば、異なるディレクトリで登録したデータも参照できる。
注意が必要なのは、localStorage .clear()を使用するとき。「他のディレクトリでの登録データも消える」ことを考慮する必要あり。


なお、localStorageのデータはcookieを削除すると消える
Google Chromeではcookieだけ消してlocalStorageを残すこともできるが、普通に閲覧履歴データの削除からcookieを消すとlocalStorageも消える。
ただし、Internet Explorer8ではcookieを削除してもlocalStorageのデータは消えない。履歴の削除から全てのデータを消してもlocalStorageは消えなかった。

<関連エントリ>
[HTML5]ダウンロードデータのキャッシュとしてWeb Storageを使う

Pre3dでHTML5 Canvas上に立体サイコロを描く手順をまとめた

3Dを描くJavaScriptライブラリPre3dを使って、HTML5 Canvas上に立体サイコロを描く手順をまとめた。

Pre3dで立体サイコロを描く
draw3dTitle1.png

自分が書いたのを読んでみると、同じような内容を何度も書くなど冗長な感じがする。
もっとシンプルに書けないのだろうか。

3D描画JavaScriptライブラリにはいろいろ種類があるが、Pre3dしか知らないので良さを主張できない。
自分がPre3dを選んだ理由は「ファイル数が少なくてソースコード読むのが楽そうだから」だし。

JavaScriptの3Dは、いずれはWebGLに収まりそうだから、他のライブラリを調べる気にはならいなぁ。

HTML5 Canvasで、画像ファイルを使わずに立体サイコロを振る

HTML5 Canvasで、3Dっぽい立体サイコロを振るプログラムを作った。
2D/3Dサイコロを振る | 無職のHTML5 Canvas
2D/3Dサイコロを振る
画像ファイルを使わずに、Pre3dというライブラリを使用して3Dサイコロを描いている。
一応Internet Explorerでも動くが、excanvas.jsを使用しているため動きが遅い。

以下、今後やりたいこと。
・動作確認しかやってないので、ツールを使ってちゃんとテストする。
・Pre3dのリファレンスやチュートリアルみたいなのを作成する。
・アニメーション時のメインループ回りを共通化するために作ったライブラリを洗練する。

まずはテストだな。

HTML5 Canvasで立体サイコロを描く

HTML5 Canvasでサイコロを振るプログラムを作ろうと思った。
平べったい2次元サイコロはすぐ作れたのだが、せっかくだから3Dっぽい立体サイコロを振ってみたい。
GIMPで立体的な画像を何種類か作ってもいいのだが、JavaScriptで描けないかと思って調べてみた。

webで3Dを描くならWebGLがいいみたいだが、自分の非力なWindowsXPノートPCで動かすのは無理があるようだ。
3Dを描くJavaScriptライブラリを探したところ、Pre3dを見つけた。
リファレンスは見当たらないが、ファイルは少ないし普通に読めるぐらいのサイズだ。

ライブラリのソースコードを読みながら描いた結果がこれ。
dice1
光が当たる方向や、光の強さを変えるメソッドが無くて、ソースコードを解析してプロパティをいじった。

次はサイコロを回転させよう。

Pre3dのソースコードを解析した結果や、サイコロを描くチュートリアルみたいなのを後でまとめようかな。

<関連エントリ>
HTML5 Canvasで、画像ファイルを使わずに立体サイコロを振る
Pre3dでHTML5 Canvas上に立体サイコロを描く手順をまとめた

積み上げ棒グラフ表示JavaScriptライブラリに、縦棒表示機能追加

先日公開した HTML5 Canvasで積み上げ棒グラフを表示するJavaScriptライブラリに、縦棒の積み上げ棒グラフを表示する機能を追加した。
積み上げ棒グラフ2

使用方法の説明、ライブラリのダウンロードは以下からどうぞ。
積み上げ棒グラフ作成 | 無職のHTML5 Canvas

Canvasサイズに応じてグラフ間の余白などが変化するため、横棒と縦棒の違いを考慮して余白を調整するところが苦労した。
ついでにグラフのタイトルを表示する機能も追加した。

昨日の記事に書いた、「表示幅に収まらない文字列をどうするか」対応も結構時間がかかった。
結局、
タイトル、説明文、エラーメッセージ → fontsizeを少しずつ減らして、できるだけ枠内に表示
データ値、見出し → 表示幅に収まらない場合は表示しない
という対応にした。
ブラウザがFirefoxのときだけfillText()のmaxWidth対応を考慮して判定しているため、他のブラウザでは表示されないのにFirefoxでは表示される場合がある。

そんなこんなで、ソースのサイズが前回公開時の倍ぐらいになってしまった。

HTML5 Canvasで 積み上げ棒グラフ を表示させてみた

私用で作ったプログラムでグラフ表示をしたくて、HTML5 Canvasで 積み上げ棒グラフ を表示するプログラムを作った。
表示結果はこんな感じ。
積み上げ棒グラフ1
AwesomeChartJS を参考にして作った。
AwesomeChartJSと同様に、Canvasのidを指定してクラスのインスタンスを生成し、プロパティにデータを格納してdraw()メソッドで表示するようにした。

せっかくだから作ったプログラムを公開したいのだが、入力値のチェックやCanvasのサイズに応じて表示サイズを変えるなど、もうちょっと修正する必要がある。
使用方法を書くのも時間がかかりそうだ。
ライセンスについてはよくわからないが、AwesomeChartJSがApache license v2.0だから、公開しても問題ないだろう。元ネタからはだいぶ変わってるし。

横棒だけでなく縦棒表示にも対応させたいが、それだと公開が更に遅れそうだ。

背景をdiv要素に描画して、HTML5 Canvasタグに重ねる

去年作った戦車が撃ちあうゲームをGAEのページへ移行した。
Canvas Tank Battle | 無職のHTML5 Canvas

移行のついでに、地形マップをCanvasタグではなく背景のdiv要素に描画するようにした。
ステージ開始時にだけ地形の描画が行われるようになるので、負荷がちょっと減る。
背景に画像などを使用する場合は、負荷の減少が大きくなるだろう。

やったことをまとめようと思ったが、CSSで要素を重ねただけなので書くことが無い。
HTMLはこんな感じ。
<div class="divcv">
  <div class="backdiv"></div>
  <div id="mcvback" class="landdiv"></div>
  <div class="wrapcv"><canvas id="mcv" class="cv" width="400" height="400"></canvas></div>
</div>
これを、親要素を position: relative 、背景要素を position: absolute にして、z-indexを指定して重ねるだけ。
CSSは以下のとおり。
<style TYPE="text/css">
/* 親要素div */
.divcv {
  position: relative;
  margin: 10px 35px 0px 20px;
  width: 400px;
}
/* Canvas */
.cv {
  border-style: solid;
  border-width: 1px;
  border-color: #808080;
}
/* Canvasを囲うdiv */
.wrapcv {
  position: relative;
  z-index: 5;
}
/* 地形マップを描画する背景div
 * マップのサイズはステージごとに変わるため、top/leftはJavaScriptで設定
 */
.landdiv {
  position: absolute;
  z-index: 1;
}
/* Canvas領域に色をつけるためだけのdiv */
.backdiv {
  position: absolute;
  top: 0;
  left: 0;
  width: 400px;
  height: 400px;
  z-index: 0;
  background-color: #F8F8FF;
}
</style>

JavaScriptで背景を描画したが、地形1マスごとにdiv要素を作ってひたすらappendしただけ。
Canvasのborder-widthの分だけ座標をズラす必要がある。
ソースは以下のとおり。jQueryはver1.4.4を使用。
/* 背景描画
 * return なし
 */
tkgame.drawland = function() {
  var grSep = tkgame.canv.grSep; // マスのサイズ(px)
  // 背景div設定
  var $mcvback = $('#' + tkgame.con.id.mcvback); // 地形マップを描画する背景div
  var bwdth = 1; // Canvasのborder-width
  $mcvback.empty(); // 全ての子要素を削除
  /* tkgame.canv.stgx, tkgame.canv.stgy は、ステージ左上の座標
   * tkgame.canv.axlen, tkgame.canv.aylen は、ステージの横幅何マスか、縦幅何マスか
   */
  $mcvback.css('top', (tkgame.canv.stgy + bwdth) + 'px');
  $mcvback.css('left', (tkgame.canv.stgx + bwdth) + 'px');
  $mcvback.css('width', (tkgame.canv.axlen * grSep) + 'px');
  $mcvback.css('height', (tkgame.canv.aylen * grSep) + 'px');
  // 地形要素設定
  var $landdiv; // 背景の地形要素
  var landname;
  /* tkgame.canv.landAr は、地形情報を格納した二次元配列
   * 詳細は tkland.js を参照
   */
  for (var ix = 0; ix < tkgame.canv.landAr.length; ix++) {
    for (var iy = 0; iy < tkgame.canv.landAr[ix].length; iy++) {
      landname = tkgame.canv.landAr[ix][iy];
      $landdiv = $('<div/>').addClass(tkgame.con.classn.landdiv);
      $landdiv.css('top', (grSep * iy) + 'px');
      $landdiv.css('left', (grSep * ix) + 'px');
      $landdiv.css('width', grSep + 'px');
      $landdiv.css('height', grSep + 'px');
      $landdiv.css('backgroundColor', tkland.type[landname].col);
      $mcvback.append($landdiv);
    }
  }
};
IE + FlashCanvas でもちゃんと表示できることを確認した。

これでひと通りの移行作業が終わった。
さて次は何を作ろうかな。

HTML5 Canvas のベジェ曲線を使ってみた

HTML5 Canvas で、四角形の中をボールが動くだけのモノを作った。
こちらで公開しています → ベジェ曲線を使ってみた

ボールが辺に当たると、辺がビョーンと伸びる。
このビョーンをベジェ曲線で描いた。
ビョーンとなっているときの動きは、F = -kx の力がかかっているとして計算した。

これを使って何か作ろうかと思ったのだが、
一次元での動きしか考慮していないので応用が利かない。

HTML5 Canvas + jQuery でドラッグ&ドロップ

タイトルのとおり、drag&drop を HTML5 Canvasで実装してみた。
こちらで公開しています → HTML5 Canvasでドラッグ&ドロップ
スタートボタンを押して実行してください。
実装してみてわかったことを簡単に解説する。

1. ソースコード
作成したソースコードは以下のとおり。jQuery は ver1.4.2 を使用。
・html および共通の Canvas 描画クラス
dtest.html  dtestcom.js
・drag&drop処理(1px 移動するごとに Canvas 更新)
dtest1.js
・drag&drop処理(1/24秒ごとに Canvas 更新)
dtest2.js

2. Canvas 更新方法について
「mousemove イベントが発生するたび更新」 → 「1px 移動するごとに更新」 → 「1/24秒ごとに更新」 の順にCPU負荷が小さくなる。

1) 1px 移動するごとに Canvas 更新
ドラッグ中状態で mousemove イベントが発生した際、現在の座標からX軸方向またはY軸方向に 1px 以上移動した場合のみ更新する。
その部分のソースコードは以下のとおり。
// 画面更新するか判定
// cx, cy ポインタのCanvas座標
// dtest1.DRAG.itemAr[dtest1.drag.item].x ドラッグしているアイテムのx座標
// dtest1.DRAG.itemAr[dtest1.drag.item].y ドラッグしているアイテムのy座標
var updSep = 1; // 何px動いたら画面更新するか
if (Math.abs(cx - dtest1.DRAG.itemAr[dtest1.drag.item].x) >= updSep ||
  Math.abs(cy - dtest1.DRAG.itemAr[dtest1.drag.item].y) >= updSep) {
  // アイテムの座標更新
  dtest1.DRAG.itemAr[dtest1.drag.item].x = cx;
  dtest1.DRAG.itemAr[dtest1.drag.item].y = cy;
  // 画面更新
  dtest1.DRAG.draw();
}
わずか1pxだが、この判定処理を入れたことで移動中のCPU負荷が(自分の環境では)1割ぐらい減った。
1px を 2px にしたら動きがぎこちなくなった。

2) 1/24秒ごとに Canvas 更新
ドラッグ中状態の場合、イベントに関係無く 1/24秒ごとに Canvas を更新する。
ドラッグしているアイテムの座標は mousemove イベントが発生するたびに更新する。
mousedownイベント発生時に、ドラッグ対象アイテムがあったら window.setInterval() でタイマー実行。
mouseup/mouseleaveイベント発生時などに window.clearInterval() でタイマー停止。

1px 移動するごとに更新する場合に比べて、移動中のCPU負荷が(自分の環境では)半分ぐらい減った。
ソースコードの修正箇所は思ったほど多くは無かった。

今後 drag&drop 以外の処理を追加していくつもりだが、
「スタート時にタイマーを実行し、イベント発生時にフラグのオンオフを行う。Canvas を更新するかはフラグをチェックして判定」
とすればCPU負荷を抑えられる上に複雑にならないだろう。

3. その他
・document上の座標をCanvas上の座標へ変換
イベントオブジェクトから取得できるマウスポインタの座標 (evt.pageX, evt.pageY) はdocument上の座標。
CanvasのDOM要素の左上座標は (offset().left, offset().top) で取得できるので、それを使ってCanvas上の座標へ変換できる。
// Canvas描画クラス
  // document上のCanvas左上座標を求める
  // <canvas>タグを認識しないケースを考慮し、同じサイズの<div>タグで囲ってそのDOM要素を取得
  // names.con.id.cvdiv は<div>タグのid
  var $cvdiv = $('#' + names.con.id.cvdiv);
  this.cvpos.x = $cvdiv.offset().left;
  this.cvpos.y = $cvdiv.offset().top;

// mousedownイベント時の処理
  // ポインタ座標をCanvas座標へ変換
  // dtest2.DRAG はCanvas描画クラスのインスタンス
  var cx = evt.pageX - dtest2.DRAG.cvpos.x;
  var cy = evt.pageY - dtest2.DRAG.cvpos.y;

・クリックした場所にドラッグ対象アイテムがあるかの判定
いわゆる当たり判定。
可能なら Canvas をグリッドでエリア分割すれば楽。

久しぶりだと思ったら、この前 HTML5 Canvas のプログラミングしたのは1ヶ月半前だった。
プロフィール

himax64

Author: 南西
30代後半の無職です。
就活もせずダラダラ生きてます。
作ったもの

最新記事
人気記事
検索フォーム
カテゴリ
月別アーカイブ
最新コメント
最新トラックバック
RSSリンクの表示
QRコード
QRコード
カウンター