Help us understand the problem. What is going on with this article?

本当にあったTouchEventの怖い話

More than 5 years have passed since last update.

事前知識: ブラウザ毎の差異

Android

2.x, 3.x

2.x, 3.x では伝統的に TouchEvent がまともに使えません。

経験上、一部端末でdocument.createEvent('TouchEvent')するとNOT_SUPPORTED_ERRを吐くなどの現象が見られ、seleniumのこのコードでも、MouseEventsを利用して無理矢理回避しています。

4.x (Stock Browser & WebView)

4.x では、やっと TouchEvent が利用できるようになります。

が、 イベントの初期化方法が後述する iOS のソレと異なっています。同じくselenium上のコードではこのように回避しているのが見受けられます。

// Android's initTouchEvent method is not compliant with the W3C spec.

とありますが、そもそもこのinitTouchEventは執筆時点(2014/10/07)でまだ標準化されていません。W3C - TouchEvents #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

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 でない場合は生えないので、以下のような Polyfill で逃げるしか現状は打つ手がない気がしています。

//!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);
}

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

initTouchEvent - Chrome, Opera 編

参考: Chromium Source

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

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

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などで代替する必要があります)

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away