addEventListener의 요상한 옵션들 중의 하나 passive

iScroll contribution을 하다가 우연히 알게된 이슈가 있어 몇자 적어본다.

Chrome 49부터 EventListenerOptions 옵션을 지원한다.

기존 addEventListener의 3번째 파라미터로 캡쳐링/버블링 여부를 제어할 수 있는 부분이 EventListenerOptions이라는 객체형태의 추가 옵션을 받을수 있게 되었다.

jQuery를 쓸 이유가 하나씩 줄고 있다. 허허허

EventListenerOptions 사용 전

document.addEventListener('touchstart', handler, false);

EventListenerOptions 사용 후

document.addEventListener('touchstart', handler, {
  capture: false,
  once: false,
  passive: false,
});

현재 크롬에서 지원하는 EventListenerOptions 옵션은 다음과 같다.

  • capture: 이벤트 캡쳐링 적용 여부. 크롬 49부터 지원
  • once: 이벤트를 한번만 호출하고 해제되는 옵션. 크롬 55부터 지원
  • passive: 스크롤 성능 향상을 위한 옵션으로 true일 경우, 스크롤을 위해 블록되는 것을 방지한다. 이 경우, preventDefault를 사용할 수 없다. 크롬 51부터 지원

이 중, passive 속성은 성능향상을 위해, 브라우저의 기능을 프로그래밍으로 제어할수 있다.

passive 속성에 대한 링크 https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

passive 속성이 false인 경우에 touchstart, touchmove와 같은 이벤트가 발생하면 preventDefault를 이용하여 실제 이벤트 자체를 막을 수 있기 때문에, 브라우저는 scroll을 계속 할지 안할지를 매번 감시해야만 한다. 하지만, passive 속성이 true일 경우에는 preventDefault를 이용하여 scroll 이벤트를 막지 않겠다고 브라우저에게 이야기하는 것과 같다. 따라서, 이 룰을 어기면 브라우저는 가차없이 다음과 같은 에러를 던진다.

errorpassive

다행이 passive 속성의 기본값은 false 이기 때문에, 기존 코드는 문제가 되지 않는다.

하지만...

Chrome 54+ 부터 EventListenerOptions의 passive 속성이 특별한 상황일 경우에는 기본값이 true로 설정된다.

document또는 body에 이벤트 리스너를 추가할때, touchstart, touchmove와 같이 스크롤이 블록되는 이벤트인 경우, passive의 기본 속성값은 true가 된다.

예를 들어 다음과 같은 코드는

document.addEventListener(
  'touchmove',
  function (e) {
    e.preventDefault();
  },
  false
);

아래와 같은 무시무시한 에러가 발생한다.

warnpassive

혹시 이런 코드가 있으면 아래 같이 수정하시면 된다. 짜잔~!

document.addEventListener(
  'touchmove',
  function (e) {
    e.preventDefault();
  },
  isPassive()
    ? {
        capture: false,
        passive: false,
      }
    : false
);

여기 사용한 isPassive 함수는 passive 속성 여부를 확인하는 유틸 함수이고, 상세 구현은 다음과 같다.

function isPassive() {
  var supportsPassiveOption = false;
  try {
    addEventListener(
      'test',
      null,
      Object.defineProperty({}, 'passive', {
        get: function () {
          supportsPassiveOption = true;
        },
      })
    );
  } catch (e) {}
  return supportsPassiveOption;
}

https://github.com/cubiq/iscroll/blob/master/demos/demoUtils.js#L2-L12

앞으로는 브라우저 내부를 점점 프로그램으로 제어할수 있는 API들이 쏟아질 것 같다. 웹개발을 하는 입장에서는 굉장히 좋은 방향성 같다.