2012年7月25日水曜日

3D Translateの実装例



Nack Labで紹介している"ダイス"、およびTech Sampleの"3D 四面"、"3D 八面"はcss trahnsformを使用して3Dの動きを実現しています。

主にPaul R. Hayes氏のExper­i­ment: 3D cube with touch ges­tures and click and dragを参考にしています。

ここでは直方体を横方に回転させる"3D 四面"を例に取り上げて説明します。

4平面を作るためにDIV

<div id="divFaces">
  <div id="cube">
    <div class="face one"   id="one">One</div>
    <div class="face two"   id="two">Two</div>
    <div class="face three" id="three">Three</div>
    <div class="face four"  id="four">Four</div>
  </div>
</div>

上記DIV対応するCSS

全体を包むdivFacesの設定

#divFaces
{
  -webkit-perspective: 420;
  -webkit-perspective-origin: 50% 100px;
}

-webkit-perspective:
視点からの距離。数値が大きいほど望遠、小さいほど広角な効果となります。

-webkit-perspective-origin:
座標軸の中心の位置。この場合、横方向中央(50%)、縦は上から100pxとなります。

divFaces内の直方体の設定

#cube {
  -webkit-transition: -webkit-transform 0.2s linear;
  -webkit-transform-style: preserve-3d;
  margin: -150px auto 0px auto;
  height: 150px;
  width: 200px;
}

-webkit-transform-style: preserve-3d;
3D座標変換を行うための設定。

-webkit-transition: -webkit-transform 0.2s linear;
アニメーションの設定。3D座標変換とは関係ない。
margin、height、widthは適宜設定する。

四面に共通の設定

.face {
  position:absolute;
  height: 100%;
  width: 100%
  padding:0px;
  background-color: rgba(50, 50, 50, 0.5);
  font-size: 27px;
  line-height: 1em;
  color: #fff;
  border: 1px solid #555;
}

position:absolute;

同一平面上に配置するため。これがないと四面のDIVが縦方向に重ならずに並ぶ。

height: 100%;
width: 100%;

cubeのサイズと一致するように100%をセット。100%である必要はなく、pxなどの設定でもよい。

各面についての設定

#cube .one {
  -webkit-transform: translateZ(100px);
}
第1面はZ軸方向に+100px移動。手前に近付いている。

#cube .two {
  -webkit-transform: rotateY(90deg) translateZ(100px);
}
第2面はY軸で+90度回転(まわれ右)してから、Z軸で100px移動(右へ移動)。

#cube .three {
    -webkit-transform: rotateY(180deg) translateZ(100px);
}
第3面はY軸で+180度回転(後向き)してから、Z軸で100px移動(遠ざかる)。

#cube .four {
    -webkit-transform: rotateY(-90deg) translateZ(100px);
}
第4面はY軸で-90度回転(まれれ左)してから、Z軸で100px移動(左へ移動)。

直方体回転

次のようなループで直方体を連続回転させることができます。

//touch, mouseの移動量を yAngle にセットします。
var yAngle = 0;

//TransformでrotateYを行い、Y軸を中心に回転させます。
function rotate() {
  yAngle += 10;
  cube.style.webkitTransform = "rotateY(" + yAngle + "deg)";
  setTimeout("rotate()", 20);
}

yAngleは初期状態からの回転角で、直前に行ったTransformからの差分ではありません。
ループで回転させる場合はsetTimeoutを使用します。

3D Transformの留意点

3Dのtransformで頭が混乱するのは主に次の二点でしょう。
  • XYZについてのtransformの順序で結果が異なる。
    上の例ではY軸で回転してからZ軸で移動しているが、Z軸で移動してからY軸で回転したのでは全く異なる結果となります。
  • rotateは座標の回転で、オブジェクトの回転ではない。
    上の例ではY軸で回転してていますが、Z軸も共に回転しているため、回転後の移動は前後の移動とは限らず、回転角に応じた方向に移動することになります。
3D 四面、八面のソースコード

//コンストラクタ
Faces = function(divFaces, divCube) {
  //直方体が配置されていDIV(divFaces)
  this.div = divFaces;
  this.cubeDiv = divCube;
  //移動量計算のため直前のtouch位置をセット。初期値=Number.MIN_VALUE
  this.x = Number.MIN_VALUE;
  //Y軸を中心とした回転角。
  this.y = 0;
  //Startイベントハンドラをセット。
  this.div.addEventListener(Faces.EventStart, Faces.touchStart, false);
  Faces.instance = this;
  Faces.div = this.div;
}

//iOS, デスクトップでイベント名を使い分ける。
Faces.EventStart = isIOS ? "touchstart" : "mousedown";
Faces.EventMove = isIOS ? "touchmove" : "mousemove";
Faces.EventEnd = isIOS ? "touchend" : "mouseup";
Faces.EventOut = isIOS ? null : "mouseout";

//Facesが作られたときの引数のdiv。イベントが発生するDIVと異なる。
Faces.div = null;

//Startイベントで作られたオブジェクト。
Faces.instance = null;

//Startイベントハンドラ。Moveイベントハンドラセット。
//thisはイベントが発生するDIV.
Faces.touchStart = function() {
  if (Faces.div == null) return;
  Faces.div.addEventListener(Faces.EventMove, Faces.touchMove, false);
  Faces.div.addEventListener(Faces.EventEnd, Faces.touchEnd, false);
  if (!isIOS) {
    Faces.div.addEventListener(Faces.EventOut, Faces.touchEnd, false);
  }
}

//Moveイベントハンドラ。
Faces.touchMove = function() {
  if (Faces.instance == null) return;
  Faces.instance.newX = event.pageX;
  with (Faces.instance) {
   if (x != Number.MIN_VALUE) {
      y += (newX - x);
      var m = y % 90;
      if (m <= 2 &&  m >= -2) {
        y -= m;
      }
      cubeDiv.style.webkitTransform = "rotateY(" + y + "deg)";
    }
    x = newX;
  }
}

//Endイベントハンドラ。End, Moveイベントハンドラ解除。
Faces.touchEnd = function() {
  if (Faces.instance == null) return;
  Faces.instance.x = Number.MIN_VALUE;
  var div = Faces.instance.div;
  div.removeEventListener(Faces.EventMove, Faces.touchMove);
  div.removeEventListener(Faces.EventEnd, Faces.touchEnd);
  div.removeEventListener(Faces.EventOut, Faces.touchEnd);
}

Faces使用例

上記のようにDIVを配置し、body.onloadなどでFacesを作る。

<body onload="new Faces(divFaces, cube)">

3D 八面の場合

八面の場合もJavaScriptは共通で、面のモデリング、CSSが異なります。

<div id="divFaces8" style="margin-top:180px">
    <div id="cube8">
      <div class="face8 f1" id="f1">One</div>
      <div class="face8 f2" id="f2">Two</div>
      <div class="face8 f3" id="f3">Three</div>
      <div class="face8 f4" id="f4">Four</div>
      <div class="face8 f5" id="f5">Five</div>
      <div class="face8 f6" id="f6">Six</div>
      <div class="face8 f7" id="f7">Seven</div>
      <div class="face8 f8" id="f8">Eight</div>
  </div>
</div>

Facesを作成するときの引数を8面のものに変更します。

<body onload="new Faces(divFaces8, cube8)">

8面の場合のCSS

#divFaces8
{
  -webkit-perspective: 250;
  -webkit-perspective-origin: 50% 80px;
}

#cube8 {
  position:relative;
  margin: -100px auto 0px auto;
  height: 133px;
  width: 100px;
  -webkit-transition: -webkit-transform 0.2s linear;
  -webkit-transform-style: preserve-3d;
}

.face8 {
  position:absolute;
  height: 120px;
  width: 100px;
  padding: 0px;
  background-color: rgba(50, 50, 50, 0.5);
  font-size: 20px;
  line-height: 1em;
  color: #fff;
  border: none;
}

#cube8 .f1 {
    -webkit-transform: translateZ(120px);
    background:gray;
}

#cube8 .f2 {
    -webkit-transform: rotateY(45deg) translateZ(120px);
    background:green;
}

#cube8 .f3 {
    -webkit-transform: rotateY(90deg) translateZ(120px);
    ---webkit-transform: translateZ(-100px);
    background:blue;
}

#cube8 .f4 {
    -webkit-transform: rotateY(135deg) translateZ(120px);
    background:yellow;
}

#cube8 .f5 {
    -webkit-transform: rotateY(180deg) translateZ(120px);
    background:red;
}

#cube8 .f6 {
    -webkit-transform: rotateY(225deg) translateZ(120px);
    background:green;
}

#cube8 .f7 {
    -webkit-transform: rotateY(270deg) translateZ(120px);
    background:blue;
}

#cube8 .f8 {
    -webkit-transform: rotateY(315deg) translateZ(120px);
    background:yellow;
}

Manifest+LocalStorageによるオフラインアプリ作成

2012年7月24日火曜日

Cache Manifestを無効にする方法



以下の記述はSafari/iOSで試した結果のもので、Safari/デスクトップでは若干異なる場合があるようです。また他のブラウザではチェックしていません。

いったん有効にしたCache Manifestを無効にする場合、単純にhtmlタグから宣言を削除しただけではうまくいきません。

既にキャッシュが有効になっている場合、htmlタグからmanifest宣言を削除してもその変更はブラウザに伝わりません。manifestファイルも更新する必要があります。
こうすると一見manifestは使われないように見えますが、ブラウザはその後もmanifestファイルをチェックしつづけ、サーバーにmanifestファイルが残っていると変更なしとなり、キャッシュを使い続けます。
こうなってからサーバーのmanifestファイルを削除しても実は手遅れで、Net不通状態と判断されるためか、キャッシュが使われ続けます。
こうして、リソースの変更が反映しないWebページになってしまうことがあります。
この動作はバグとも言えそうなので、今後変更される可能性があるでしょう。

次の手順で行うと、manifestファイルを参照しなくなります。

  1. サーバーからmanifestファイルを削除またはりネーム
  2. ブラウザからアクセス(いったんFile Not Foundの状態を作る)
  3. htmlタグからmanifest宣言を削除

開発環境では上記手順を実行することが可能ですが、実稼働環境ではブラウザからのアクセスを確認することが困難です。次善の策として、次のような手順が考えられます。


  1. htmlタグのmanifest宣言のファイル名を存在しないファイル名に変更し、manifestファイルを更新する。
    <html manifest="a.manifest">を<html manifest="-.manifest">のように変更します。
    拡張子はMIME TYPEに登録されているものにしておきます。さもないと、再度manifestを有効にしたいときに苦労します。Safari/iOSではキャッシュデータの削除を行わないと回復できませんでした。
  2. このまましばらく運用し、対象ブラウザが全て一度は"-.manifest"にアクセスしたと判断できたら、htmlタグからmanifest宣言を削除、manifestファイルを更新する。

manifest="-"へのアクセスの負荷は軽いので、1のままでも実害はほとんどないでしょう。再度Cacheを有効にしたい場合はmanifest宣言のファイル名を元に戻します。

Safari/iOSからIISにアクセスしたときのログ

設定
a.manifestファイルでindex.htmlとXmlReq.jsをキャッシュ。
NETWORK:*を設定し、XMLHttpRequestは常にNETWORKから取得。
acuse指示XMLHttpRequestでdata.txtにアクセス。

Cacheが有効な場合
manifestファイルのstatush304(Not Modified)
01:57:45 172.30.10.11 GET /a/a.manifest 304
01:57:45 172.30.10.11 GET /a/Resources/data.txt 200

index.htmlのhtmlタグからmanifest宣言を削除
manifestが更新されていないのでキャッシュが使用される。
01:58:42 172.30.10.11 GET /a/a.manifest 304
01:58:42 172.30.10.11 GET /a/Resources/data.txt 200

a.manifest更新
キャッシュが更新される。
01:59:30 172.30.10.11 GET /a/a.manifest 200
01:59:30 172.30.10.11 GET /a/Resources/data.txt 200
01:59:30 172.30.10.11 GET /a/index.html 200
01:59:30 172.30.10.11 GET /a/Resources/XmlReq.js 304
01:59:30 172.30.10.11 GET /a/index.html 200

再度アクセス
indexから宣言が削除されているがmanifest参照は有効なまま。index.html、XmlReq.jsはキャッシュが使用される。
01:59:37 172.30.10.11 GET /a/a.manifest 304
01:59:37 172.30.10.11 GET /a/Resources/data.txt 200

XMLHttpRequestでdata.txtにアクセス
既にmanifestチェック済みなのでdata.txtのみアクセスされる。
02:44:27 172.30.10.11 GET /a/Resources/data.txt 200

index.htmlのmanifest宣言復活、a.manifest更新
02:49:18 172.30.10.11 GET /a/a.manifest 200
02:49:18 172.30.10.11 GET /a/Resources/data.txt 200
02:49:18 172.30.10.11 GET /a/index.html 200
02:49:18 172.30.10.11 GET /a/Resources/XmlReq.js 304
02:49:18 172.30.10.11 GET /a/index.html 200

a.manifest削除
manifestファイルのstatusは404(Not Found)となる。
この時点ではindex.html、XmlReq.jsはキャッシュが使用される。
02:50:32 172.30.10.11 GET /a/a.manifest 404
02:50:32 172.30.10.11 GET /a/Resources/data.txt 200

index.htmlからmanifest宣言を削除
manifest参照は行われず、index.html、XmlReq.jsへのアクセス発生。
02:53:37 172.30.10.11 GET /a/index.html 200
02:53:37 172.30.10.11 GET /a/Resources/XmlReq.js 304
02:53:39 172.30.10.11 GET /a/Resources/data.txt 200

manifestが有効な状態で、index.htmlのmanifest宣言のファイル名を存在しないもの("-.manifest")に変更、manifestファイル更新
キャッシュが更新される。
02:58:48 172.30.10.11 GET /a/a.manifest 200
02:58:48 172.30.10.11 GET /a/Resources/data.txt 200
02:58:48 172.30.10.11 GET /a/index.html 200
02:58:48 172.30.10.11 GET /a/Resources/XmlReq.js 304
02:58:48 172.30.10.11 GET /a/index.html 200

再度アクセス
"-.manifest"は存在しないのでstatusは404となる。一度もmanifestファイルが読まれていない状態のため、キャッシュは無効になり、index.html、XmlReq.jsへのアクセスが発生する。
03:00:41 172.30.10.11 GET /a/index.html 304
03:00:41 172.30.10.11 GET /a/- 404
03:00:41 172.30.10.11 GET /a/Resources/XmlReq.js 304
03:00:41 172.30.10.11 GET /a/Resources/data.txt 304

index.htmlのmanifest宣言を削除
manifest参照は行われず、index.html、XmlReq.jsへのアクセス発生。
03:03:24 172.30.10.11 GET /a/index.html 200
03:03:24 172.30.10.11 GET /a/Resources/XmlReq.js 304
03:03:24 172.30.10.11 GET /a/Resources/data.txt 200

Cache Manifestを無効にする方法

2012年7月22日日曜日

Cache Manifest使用時のXMLHttpRequest



Cache Manifestを使用したオフラインアプリでもXMLHttpRequestを使用することができます。次のような点に注意すれば、XMLHttpRequestを利用してCacheを残しつつ、オンライン時に一部のデータだけ更新することができます。
以下の方法はSafari/iOSでのみチェックしています。

1.Cache Manifestの設定
次の設定をCache Manifestに含める。
NETWORK:
*
これを設定しないと、Cacheが有効なときはXMLHttpRequestのレスポンスが空となります。

2.XMLHttpRequeststatusのstatus=0チェック
Cacheが有効なときはXMLHttpRequeststatusのstatusコードが0となり、レスポンスが空になります。
ただし、XMLHttpRequeststatusのQuery StringがついたURLの場合、ホーム画面のアイコンからアクセスする場合はstatus=0ですが、Safariのアドレスバーからアクセスするとstatus=200となり、レスポンスはCacheされているデータとなります。

3.localStroageとの併用がお勧め
status=0のときにネットワークから取得したデータを使うことができるようにするため、XMLHttpRequeststatusで取得したデータはlocalStroageに保存しておくのがお勧めです。

XMLHttpRequest実装例

XmlReq = function(url) {
  this.url = url;
}

//elm = responseのcontentをセットする要素。
XmlReq.prototype.send = function(elm) {
  var req = this.createHttpRequest();
  req.open("GET", this.url, true); //true = 非同期。
  req.onreadystatechange =
    function() { //受信時に起動するイベントハンドラ
      if (req.readyState == 4) { //4 = 受信完了 (サーバ処理終了)
        var content = null;
        if (req.status == 200) {
          content = req.responseText;
          localStorage["content"] = content;
        } if (req.status == 0) {
          content = localStorage["content"];
        }
        if (content != null) {
          if (elm.tagName == "INPUT") {
            elm.value = content;
          } else if (elm.tagName == "DIV") {
            elm.innerText = content;
          } else {
            //適宜追加
          }
        }
      }
    };
  req.send(null);
}

XmlReq.prototype.createHttpRequest = function() {
  if(window.ActiveXObject){ //Win IE
    try {
      return new ActiveXObject("Msxml2.XMLHTTP");
    } catch(e) { //MSXML2以前
      return null;
    }
  } else if(window.XMLHttpRequest){
    //Win ie以外のXMLHttpRequestオブジェクト実装ブラウザ用
    return new XMLHttpRequest();
  } else {
    logDiv.innerHTML += "<br><null>";
    return null;
  }
}

XmlReq.prototype.toString = function() {
  return this.url;
}

XmlReqの利用例

function sendRequest() {
  var ID = new Date().getTime();
  var req = new XmlReq("http://" + location.host + "/SampleApp/Resources/data.txt?ID=" + ID);
  req.send(hiddenField);
}
function showData() {
  alert(hiddenField.value);
}

<input type="hidden" id="hiddenField">

2012年7月11日水曜日

localStorageの実装例



NackLabに載せているHTMLアプリは、ほとんどがlocalStorageを利用しています。その中の一例を紹介します。

localStorageのメソッドの拡張例(エラー処理などを簡略化してあります)
localStorageにptorotypeでメソッドを追加することができないため、LocalStorageをつくり、Stringの値と、それをNumber、boolに変換して返すfunctionを追加しています。

//コンストラクタ。この例ではLocalStorageを宣言しているだけ。
LocalStorage = function() { }

//key、valueのセット。
LocalStorage.set = function(key, val) {
  localStorage[key] = val;
}

//LocalStorageからデータを読み出す。Stringが返される。
LocalStorage.stringValue = function(key, defaultValue) {
  var str = localStorage[key];
  if (str == null) return defaultValue;
  return str;
}

//keyの値を数値として返す。値が設定されていない場合は、数値化でエラーとなった場合はdefaultValueを返す。
LocalStorage.numberValue = function(key, defaultValue) {
  var val = null;
  try {
    val = localStorage[key];
    if (val == null) val = defaultValue;
    return new Number(val);
  } catch(e) {
    return defaultValue;
  }
}

//keyの値をboolとして返す。値が設定されていない場合はdefaultValueを返す。
LocalStorage.boolValue = function(key, defaultValue) {
  var val = null;
  val = localStorage[key];
  if (val == null) val = defaultValue;
  return (val.toLowerCase() == "true");
}

LocalStorageの呼び出し例

//body.onloadで呼ぶ
function init() {
  divCount.innerText = LocalStorage.numberValue("count", 5);
  cbTraining.checked = LocalStorage.boolValue("training", false);
}

//onunloadで呼ぶ。
function save() {
  LocalStorage.set("count", divCount.innerText);
  LocalStorage.set("training", cbTraining.checked);
}

localStorageの実装例

2012年7月3日火曜日

Site.Masterの選択中のタブStyle変更



VisualStudio 2010でWebアプリケーション プロジェクトを追加すると、Site.Masterによ
りタブでページ切り替えを行うテンプレートがコピーされます。
タブクリックでページを切り替えたあと、選択中のページのタブのスタイルを変更したく
なるのですが、なかなか思うようにいきませでした。次のようにすると変更できます。

SiteMaster.csにメソッド追加

protected void Page_Load(object sender, EventArgs e)
{
    NavigationMenu.Load += new EventHandler(NavigationMenu_Laod);
}

protected void NavigationMenu_Laod(object sender, EventArgs e)
{
    string requestUrl = Request.Url.Segments[Request.Url.Segments.Length - 1];
    foreach (MenuItem item in ((Menu)sender).Items)
    {
        if (item.NavigateUrl.EndsWith(requestUrl))
        {
            item.Selected = true;
            break;
        }
    }
}

SiteMaster.aspxにstyle追加

    <style type="text/css">
        div.menu ul li a.selected
        {
        background-color:LightYellow;
        color:Black;
        }
        /*disabledしたタブのstyle*/
        div.menu ul li a.aspNetDisabled
        {
        background-color:#CCCCCC;
        color:#777777;
        }
    </style>