みかづきブログ その3

3ヶ月つづけてみました。

いつの間にかaddEventListenerの第3引数にuseCaptureではなくAddEventListenerOptionsを渡せるようになっている。 😮

ことのはじまり

きっかけはこの警告でした。

f:id:kimizuka:20170605093323p:plain

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

touchmoveが発火する度になんか警告がでるなぁと。
親切にもURLも表示してくれているのでアクセスしてみると。

AddEventListenerOptions defaults passive to false. With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..

If the value is explicitly provided in the AddEventListenerOptions it will continue having the value specified by the page.

This is behind a flag starting in Chrome 54, and enabled by default in Chrome 56. See https://developers.google.com/web/updates/2017/01/scrolling-intervention

Chrome Platform Status より引用

ふむふむ。Google翻訳で翻訳してみると。

AddEventListenerOptionsは、passiveをfalseにデフォルト設定します。この変更を開始すると、ドキュメントに追加されたtouchmoveリスナーは、デフォルトでpassive:trueに設定されます(preventDefaultの呼び出しは無視されます)。

値が明示的にAddEventListenerOptionsで提供されている場合、値はページで指定された値を持ち続けます。

これはChrome 54から始まるフラグの後ろにあり、Chrome 56ではデフォルトで有効になっています。https://developers.google.com/web/updates/2017/01/scrolling-intervention

なるほど。documentaddEventListenerするtouchmovehandler内でpreventDefaultを呼び出す場合は、AddEventListenerOptionspassiveの値をfalseにしないといけないんですな。


passiveオプションが必要な理由

理由を調べてみると、passiveオプションを渡さないと、handlerを実行し切るまでスクロールしてよいかどうか判断がつかず、スクロールがもたつくからイベントを登録する際に明示的に教えてあげようよというものらしいです。確かに昨今独自スクロールのサイト増えてきてますからね。


AddEventListenerOptionsの渡し方

んでもって、AddEventListenerOptionsはどうやって渡せばいいのか調べてみたところ、

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener [, useCapture]);

EventTarget.addEventListener - Web API インターフェイス | MDN より引用

developer.mozilla.org

どうやら第3引数でオブジェクトを渡すようですね。
第3引数はバブリングフェーズに発火するか、キャプチャリングフェーズに発火するかをBooleanで渡すものだと思ってましたが、
時代は変わりますね。

optionsとして渡すオブジェクトでは、

capture: Boolean 値で true を指定すると、指定されたタイプのイベントが、DOM ツリーで下に位置する任意の EventTarget に発送される前に、登録したリスナーに発送されます。
 
once: listener が追加後にたかだか1回しか実行されないことを {jsxref("Boolean")}} 値で指定します。true を指定すると、listener は一度実行された時に自動的に削除されます。
 
passive: listener が preventDefault() を呼び出さないことを表す Boolean 値です。true を指定すると、ユーザーエージェントはその呼び出しを無視し、コンソールに警告を出力します。

が渡せるようです。
EventTarget.addEventListener - Web API インターフェイス | MDN より引用


非対応ブラウザとの互換

ただし、AddEventListenerOptionsはすべてのブラウザに対応しているわけではなく、非対応ブラウザの第3引数にオブジェクトを渡してしまうと、useCaptureがtrueになってしまいます。(オブジェクトがfalsyな値ではないため)
非対応ブラウザにはAddEventListenerOptionsのcaptureの値を渡したい。
そんな方法を探していたら、 こちらの記事 を見つけました。

blog.jxck.io

なるほど。getterをつかう方法がイケてるわけですね。

ver supportsPassive = false;

try {
  ver opt = {
    get passive() {
      supportsPassive = true;
    }
  },
  handler = function() {};

  window.addEventListener("checkpassive", handler, opt);
  window.removeEventListener("checkpassive", handler, opt);
} catch (err) {

}

function addEventListenerWithOptions(target, type, handler, options) {
  ver optionsOrCapture = options;

  if (!supportsPassive) {
    optionsOrCapture = options.capture;
  }

  target.addEventListener(type, handler, optionsOrCapture);
};

windowにイベントを張りっぱなしなのも気持ち悪いのでとりあえず剥がしてみました。

昔は、addEventListenerとattachEventを振り分ける関数をつくったりしていましたが、
またしばらくはAddEventListenerOptionsに対応しているか否かを判定する必要があるのかもしれないですね。