本当にあったTouchEventの怖い話

## 事前知識: ブラウザ毎の差異
 
### Android
 
#### 2.x, 3.x
 
2.x, 3.x では伝統的に TouchEvent がまともに使えません。
 
経験上、一部端末で`document.createEvent('TouchEvent')`すると`NOT_SUPPORTED_ERR`を吐くなどの現象が見られ、seleniumの[このコード](https://code.google.com/p/selenium/source/browse/javascript/atoms/events.js#523)でも、`MouseEvents`を利用して~~無理矢理~~回避しています。
 
#### 4.x (Stock Browser & WebView)
 
4.x では、やっと TouchEvent が利用できるようになります。
 
**が、** イベントの初期化方法が後述する iOS のソレと異なっています。同じくselenium上のコードでは[このように](https://code.google.com/p/selenium/source/browse/javascript/atoms/events.js#536)回避しているのが見受けられます。
 
> // Android's initTouchEvent method is not compliant with the W3C spec.
 
とありますが、そもそもこの`initTouchEvent`は執筆時点(2014/10/07)でまだ標準化されていません。[W3C - TouchEvents #NOTE_3](http://www.w3.org/TR/touch-events/#h_note_3)

(この辺り、詳しくは後述)
 
#### 4.x (Chrome for Android & Chromium WebView)

`document.createEvent('TouchEvent')`でタッチイベントを生成することができます。  
**が、** Stock Browserと同様、`initTouchEvent`の引数がiOSと異なります。(後述)

コンストラクタとしての`TouchEvent`, `Touch`, `TouchList`が存在しますが、いずれもnewすると`Illegal constructor`として怒られます。(どんな引数を渡しても駄目)

(多分)Opera Mobileも同一の挙動を示すと思われます。

 
### iOS
 
バージョン毎にわける必要はありません。
 
非常に優秀で革新的な iOS では、iOS2.0 の頃から`TouchEvent`を利用することができます。[Safari Developer Library - TouchEvent Class Reference](https://developer.apple.com/library/safari/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html#//apple_ref/javascript/instm/TouchEvent/initTouchEvent)
 
 
### Windows & Mac
 
#### Internet Exproler 11
 
TouchEvent は使えません。PointerEvent なら使えます。PointerEvent を使えば良いんじゃないでしょうか。
 
#### Chrome(+Opera), Firefox
 
通常は TouchEvent を使用できませんが、開発ツール上で TouchEvent のエミュレートをONにすることで`document.createEvent('TouchEvent')`することができます。

コンストラクタとしての`TouchEvent`, `Touch`, `TouchList`が存在しますが、いずれもnewすると`Illegal constructor`として怒られます。(どんな引数を渡しても駄目)

また、ChromeとFirefoxで`initTouchEvent`の引数の順番が異なります。(後述)
 
#### Safari

`document.createEvent('TouchEvent')`, `TouchEvent`, `Touch`, `TouchList` などのコンストラクタともにそのままでは使えません。

iOSシミュレータもしくはiOSの実機を接続して、その端末のSafariのWebインスペクタを開いた場合は全て利用することができます。(= PCサイトでは使えない)



## TouchEventを生成する

先ほどさらっと書きましたが、タッチイベントを生成する際の`initTouchEvent`メソッドが標準化されていないため、 **各ブラウザ毎に引数が違う** という血も涙もない状況です。また2.x, 3.x系のAndroidもサポートするのであれば、MouseEventを代替として用いる必要もあります。

さらに、`initTouchEvent`を実行する際には`TouchList`という配列 like なオブジェクトを渡す必要があります。これは、`touches`, `targetTouches`, `changedTouches`という現在の指の情報として TouchEvent に付与されます。  
これらを生成するには、`document.createTouch`, `document.createTouchList`のメソッド、もしくは Safari のみ `Touch`, `TouchList` コンストラクタで生成することができます。  
**が、** `document.createTouch`, `document.createTouchList` は現在の Document オブジェクトが [DocumentTouch](https://developer.mozilla.org/ja/docs/Web/API/DocumentTouch) でない場合は生えないので、以下のような Polyfill で逃げるしか現状は打つ手がない気がしています。

```javascript
//!Document.prototype.createTouch by w3c spec createTouch (https://developer.mozilla.org/ja/docs/Web/API/DocumentTouch.createTouch)
if (!Document.prototype.createTouch) {
    Document.prototype.createTouch = function(view, target, identifier, pageX, pageY,
                                              screenX, screenY, clientX, clientY,
                                              radiusX, radiusY, rotationAngle, force) {
        return {
            clientX       : clientX,
            clientY       : clientY,
            force         : force,
            identifier    : identifier,
            pageX         : pageX,
            pageY         : pageY,
            radiusX       : radiusX,
            radiusY       : radiusY,
            rotationAngle : rotationAngle,
            screenX       : screenX,
            screenY       : screenY,
            target        : target,
        };
    };
}

//!Document.prototype.createTouchList by w3c spec createTouchList (https://developer.mozilla.org/ja/docs/Web/API/DocumentTouch.createTouch)
if (!Document.prototype.createTouchList) {
    (function(global) {
        function TouchList(touches) {
            this.length = 0;
            if (!touches) {
                return this;
            }
            // list type argument
            else if (touches.length) {
                var touch;

                for (var i = 0, iz = touches.length; i < iz; i++) {
                    touch = touches[i];
                    this[i] = touch;
                }
                this.length = iz;
            }
            else {
                this[0] = touches;
                this.length = 1;
            }
        }
        TouchList.prototype = {
            constructor: TouchList,
            identifiedTouch: function(id) {
                var that = this;

                for (var key in that) if (!global.isNaN(global.parseInt(key)) && that.hasOwnProperty(key))  {
                    if (that[key].identifier == id) {
                        return that[key];
                    }
                }
                return undefined;
            },
            item: function(index) {
                return this[index];
            }
        };

        Document.prototype.createTouchList = function(touches) {
            return new TouchList(touches);
        };
    })(window);
}
```

- [DocumentTouch,createTouch](https://developer.mozilla.org/ja/docs/Web/API/DocumentTouch.createTouch)
- [DocumentTouch.createTouchList](https://developer.mozilla.org/ja/docs/Web/API/DocumentTouch.createTouch)

以下では、"tap"というイベントを生成する例を記します。


### `initTouchEvent` - Chrome, Opera 編 

参考: [Chromium Source](https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/events/TouchEvent.cpp&q=initTouchEvent&sq=package:chromium&type=cs&l=58)

```javascript
var touch = document.createTouch(/* 引数省略 */);
var touches = document.createTouchList(/* 引数省略 */);
var event = document.createEvent('TouchEvent');

event.initTouchEvent(
    touches,              // {TouchList} touches
    touches,              // {TouchList} targetTouches
    touches,              // {TouchList} changedTouches
    'tap',                // {String}    type
    document.defaultView, // {Window}    view
    0,                    // {Number}    screenX
    0,                    // {Number}    screenY
    0,                    // {Number}    clientX
    0,                    // {Number}    clientY
    false,                // {Boolean}   ctrlKey
    false,                // {Boolean}   alrKey
    false,                // {Boolean}   shiftKey
    false                 // {Boolean}   metaKey
);
```

### `initTouchEvent` - Safari 編

参考: [Safari Developer Library - TouchEvent Class Reference](https://developer.apple.com/library/safari/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html#//apple_ref/javascript/instm/TouchEvent/initTouchEvent)

```javascript
var touch = document.createTouch(/* 引数省略 */);
var touches = document.createTouchList(/* 引数省略 */);
var event = document.createEvent('TouchEvent');

event.initTouchEvent(
    'tap',                // {String}    type
    true,                 // {Boolean}   canBubble
    true,                 // {Boolean}   cancelable
    document.defaultView, // {Window}    view
    1,                    // {Number}    detail
    0,                    // {Number}    screenX
    0,                    // {Number}    screenY
    0,                    // {Number}    clientX
    0,                    // {Number}    clientY
    false,                // {Boolean}   ctrlKey
    false,                // {Boolean}   altKey
    false,                // {Boolean}   shiftKey
    false,                // {Boolean}   metaKey
    touches,              // {TouchList} touches
    touches,              // {TouchList} targetTouches
    touches,              // {TouchList} changedTouches
    0,                    // {Number}    scale(0 - 1)
    0                     // {Number}    rotation
);
```

### `initTouchEvent` - Firefox 編

参考: [mozilla-central mozilla/dom/events/TouchEvent.h](http://lxr.mozilla.org/mozilla-central/source/dom/events/TouchEvent.h#108)

```javascript

var touch = document.createTouch(/* 引数省略 */);
var touches = document.createTouchList(/* 引数省略 */);
var event = document.createEvent('TouchEvent');

event.initTouchEvent(
    'tap',                // {String} type
    true,                 // {Boolean} canBubble
    true,                 // {Boolean} cancelable
    document.defaultView, // {Window} view
    1,                    // {Number} detail
    false,                // {Boolean} ctrlKey
    false,                // {Boolean} altKey
    false,                // {Boolean} shiftKey
    false,                // {Boolean} metaKey
    touches,              // {TouchList} touches
    touches,              // {TouchList} targetTouches
    touches               // {TouchList} changedTouches
);
```

### 現実的な話

例えばAndroid2.3〜, iOS6〜、かつPCで表示した場合も問題なくタッチイベントを生成する場合は

- Android2.x, 3.xだったら
    - `document.createEvent('MouseEvent')`して、引数をMouseEventに合わせて`initMouseEvent`して、TouchEventっぽく見せかけて(touches, targetTouches, changedTouchesをプロパティに追加)発火
- Android4.x (Stock Browser & Chrome)
    - `document.createEvent('TouchEvent')`して、引数をChrome用のもので`initTouchEvent`して発火
- iOS
    - `document.createEvent('TouchEvent')`して、引数をSafari用のもので`initTouchEvent`して発火
- PC Chrome & Opera
    - Polyfillで`createTouch`, `createTouchList`を生やした後、Android4.xと同一の処理
- PC Safari
    - Polyfillで`createTouch`, `createTouchList`を生やした後、Android2.x, 3.xと同一の処理
- PC Firefox
    - Polyfillで`createTouch`, `createTouchList`を生やした後、引数をFirefox用のもので`initTouchEvent`して発火
- PC Internet Exproler
    - Polyfillで`createTouch`, `createTouchList`を生やした後、Android2.x, 3.xと同一の処理

というのが現実解でしょうか。

しかし、Chrome などで UserAgent を変更した場合には **iOS のフリをした Chrome** という状況になるのでかなり判定も難しくなるのでは無いかと思います。


## 答え

**諦めて`CustomEvent`使え**

(Android2.xは`CustomEvent`ないのでそれも`UIEvent`などで代替する必要があります)