Use passive scroll listeners

Mark touch and wheel listeners passive unless they cancel scroll.

From nextjs-pagespeed.

What the scroll waits for

Press Flick, then change the handler cost.

passive: falseBad
passive: trueGood

Scroll the real thing

Scroll each list with a wheel or finger.

passive: falseBad
    0 events
    passive: trueGood
      0 events
      Main-thread frame timeIdle
      list.addEventListener("wheel", onWheel, { passive: true });
      list.addEventListener("touchmove", onTouchMove, { passive: true });
      canvas.addEventListener("wheel", zoom, { passive: false });

      Stop scroll chaining with CSS

      Scroll each list past its end.

      JS: preventDefaultBad

      Page leaked: 0

      wheel, { passive: false }
      CSS: overscrollGood

      Page leaked: 0

      .panel  { overscroll-behavior: contain; }
      .canvas { touch-action: none; }
      .slider { touch-action: pan-y; }

      Why it matters, pros and cons

      Why it matters

      A non-passive listener makes scrolling wait for JavaScript every time.

      Pros

      • Scroll starts next frame, however busy JS is.
      • One option flag, same handler body.
      • React already attaches wheel and touch passively.

      Cons

      • Passive listeners cannot cancel custom zoom or drag.
      • The handler itself gets no cheaper.
      • No effect on the scroll event itself.