iPad & IPod & iPhone の WEBアプリで、クリックができない!を解決する。その4

<!DOCTYPE html>
<title>クリック・モドキ</title>
  <meta name="viewport" content="width=640">
  <style>
  h1 {
    font-size: large;
  }
  h3 {
    margin-bottom : 0;
  }
  h3 + p {
    font-size: small;
    color: gray;
    margin : 0 2em;
  }
  em {
    color: green;
    font-size:x-large;
  }
  li.focus {
    border-bottom : 2px red dotted;
  }
  </style>
<body>

<h1>
  <em>iPod</em> &amp;
  <em>iPad</em> &amp;
  <em>iPhone</em> で、クリックイベントを有効にする
</h1>

<h3>クリックイベントが発生する状態</h3>
<ul>
  <li>document をクリック(タップ)した時
  <li>フォームの input要素(button)など
  <li>要素のインラインに書いたイベント
</ul>

<h3>クリックイベントの代わりに、touchstart, touchmove, touchend を利用する</h3>
<p>(3つのイベントを組み合わせて、クリックイベントを実現する)
<ul>
  <li>touchstart イベントが発生したら、スタート位置を記憶する
  <li>touchmove を監視し、現在位置との差が範囲外なら、止める
  <li>touchend イベントオブジェクトからは、位置が拾えないので、スタート位置を基準とし、
      クリックイベントを発生させる
</ul>

<h3>使い方</h3>
<ul>
  <li>
    <p>面倒なので略。
</ul>

<script>

document.addEventListener ('click', function (e) {
  var n = e.target;
  n.className = n.className ? '': 'focus';
}, false);

//_________________________________

(function () {
  
  // Apple mobile 簡易機種チェック
  function isAppleMobile () { 
    return (/iPhone|iPad|iPod/.test (navigator.userAgent));
  }
  
  //______________
  
  // Eventer 用
  // object の複製
  function createObject (arg, args) {
    var Func = new Function;
    var result;
    
    Func.prototype = arg;
    result = new Func;
    
    if (args)
      defineProperties.call (result, arg);

    return result;
  }

  // this に args の固有プロパティを追加する
  function defineProperties (args) {
    for (var n in args)
      if (args.hasOwnProperty (n))
        this[n] = args[n];
  }
  
  // 初期化
  function reset () {
    this.args = createObject (DEF_EVENT_ARG);
  }
  
  // 引数の設定
  function configure (parm/*Object*/) {
    defineProperties.call (this.args, parm);
  }
  
  // イベント発火
  function fire (target) {
    var e = DOC.createEvent ('MouseEvents');
    var p = this.args;
    
    e.initMouseEvent ('click', p.bubbles, p.cancelable, VIEW, p.detail,
                      p.screenX, p.screenY, p.screenX, p.screenY,
                      p.ctrlKey, p.altKey, p.shiftKey, p.metaKey, p.button, null);
    target.dispatchEvent (e);
  }

  //______________
  // Emulator 用
  
  // 引数の変更
  function emu_configure (parm/*Object*/) {
    configure.call (Eventer, parm);
  }
  
  // タップの座標がエリア内ならば true を返す
  function isInside (x, y) {
    return ((Math.abs (x - this.x) < RANGE) &&
            (Math.abs (y - this.y) < RANGE));
  }
  
  // touch 関連のイベントハンドラー
  function emu_handler (event) {
    if (this.disabled)
      return;

    var touch = event.touches;
    var eType = event.type;
    var x, y;
    var t;

    if (STATE) {
      if (('touchmove' === eType) && (touch = touch[0])) {
        x = touch.pageX;
        y = touch.pageY;
        if ((STATE = isInside.call (POINT, x, y))) {
          POINT.x = x;
          POINT.y = y;
        }
      }
      else if ('touchend' === eType) {
        t = event.target;
        if (3 === t.nodeType) {
          this.fire (t.parentNode, POINT);
          return event.preventDefault ();
        }
      }
    }
    else if (('touchstart' === eType) && (1 === touch.length) && (touch = touch[0])) {
      POINT.x = touch.pageX;
      POINT.y = touch.pageY;
      STATE = true;
    }
  }

  
  // クリック実行
  function emu_fire (target, point) {
    Eventer.configure ({'screenX': point.x, 'screenY': point.y});
    Eventer.fire ((3 === target.nodeType) ? target.parentNode: target);
      // ※ touch 関連のイベントでは、テキストノードを返す
  }

  //___________________________________________________

  if (isAppleMobile ()) {

    var DOC   = document;
    var VIEW  = DOC.defaultView;

    var RANGE = 10; // タップした場所からの範囲
    var STATE = false; // true なら、クリック監視

    var POINT = { //タップした座標
      'x' : 0,
      'y' : 0
    }; 

    var DEF_EVENT_ARG = { // クリックイベントの初期値
      'bubbles'       : true,
      'cancelable'    : true,
      'detail'        : 1,
      'screenX'       : 0,
      'screenY'       : 0,
      'ctrlKey'       : false,
      'altKey'        : false,
      'shiftKey'      : false,
      'metaKey'       : false,
      'button'        : 0,
    };


    // イベント処理
    var Eventer = new Function;

        Eventer.args      = null;
        Eventer.reset     = reset;
        Eventer.configure = configure;
        Eventer.fire      = fire;
        
    // touch 関連のイベントからクリック処理を真似る
    var Emulator = new Function;
        
        Emulator.disabled    = false;
        Emulator.configure   = emu_configure;
        Emulator.handleEvent = emu_handler;
        Emulator.fire        = emu_fire;
        
    //___________________

    DOC.addEventListener ('touchstart', Emulator, false);
    DOC.addEventListener ('touchmove', Emulator, false);
    DOC.addEventListener ('touchend', Emulator, false);

    Eventer.reset ();

    this.ClickEmulator = Emulator;
  }
})();

</script>