1
votes

I would like to listen to the mouse wheel, and if the mouse is position above a certain element, cancel the default and do my stuff instead.

For now I just want to log the event and prevent it:

  const handleWheel = e => {
    console.log(e);
    e.preventDefault();
  };

Using svelte this is what I came up with:

<svelte:window on:wheel={handleWheel} />

Unfortantly this only lets me log the event, and gives this error message:

Unable to preventDefault inside passive event listener invocation.

Using pure JS the solution is to add a parameter:

window.addEventListener("wheel", handleWheel, { passive: false});

But I can find no way in the svelte documentation that mentions how this can be done. So how would you do this the svelte way?

2

2 Answers

3
votes

Well, your code does work as it is for me...

<script>
  const handleWheel = e => {
    console.log(e);
    e.preventDefault();
  };
</script>

<svelte:window on:wheel={handleWheel} />

See in the REPL.

Maybe it's your browser, but I guess it might also be the REPL itself (if that's what you were using) that may have some issues with <svelte:window /> events, and displays outdated logs...

Actually, your solution would do the opposite of what you want, and cause the warning:

window.addEventListener("wheel", handleWheel, { passive: true });

From MDN:

passive

A Boolean which, if true, indicates that the function specified by listener will never call preventDefault(). If a passive listener does call preventDefault(), the user agent will do nothing other than generate a console warning.

With that out of the way, Svelte's way to add options to event listeners is with modifiers.

For example:

<svelte:window on:wheel|preventDefault={handleWheel} />

<svelte:window on:wheel|passive={handleWheel} />

They're combinable, but preventDefault and passive don't go well together, so let's use another one:

<svelte:window on:wheel|preventDefault|once={handleWheel} />

See Svelte's docs on events for available modifiers, and details:

The following modifiers are available:

preventDefault — calls event.preventDefault() before running the handler

stopPropagation — calls event.stopPropagation(), preventing the event reaching the next element

passive — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)

capture — fires the handler during the capture phase instead of the bubbling phase

once — remove the handler after the first time it runs

Modifiers can be chained together, e.g. on:click|once|capture={...}.

3
votes

Currently, there's no way to do this idiomatically with an event handler, because Chrome broke the web by making certain event listeners passive by default. There's an open Svelte issue that would address this.

In the meantime, the most idiomatic way would probably be to use an action:

<script>
  let scrollable = true;

  const wheel = (node, options) => {
    let { scrollable } = options;

    const handler = e => {
      if (!scrollable) e.preventDefault();
    };

    node.addEventListener('wheel', handler, { passive: false });

    return {
      update(options) {
        scrollable = options.scrollable;
      },
      destroy() {
        node.removeEventListener('wheel', handler, { passive: false });
      }
    };
  };
</script>

<svelte:window use:wheel={{scrollable}} />

Demo here.