-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Description
Describe the bug
When creating an application in svelte which requires drawing to a canvas controlled by both mouse and touch device, I found that mobile devices require a call to e.preventDefault in order to prevent the screen from scrolling and refreshing the page. However, when I call e.preventDefault to any of the attribute events used in drawing to the canvas, it is ignored, and gives a different warning based on browser to let you know that e.preventDefault was ignored due to a listener being registered as passive. I've provided a minimal recreation of how I got this warning.
Running the code on a mobile device (though the warning can't be seen), shows that e.preventDefault was ignored since the page is scrolling when I attempt to draw. The browsers on mobile used were safari and chrome.
I tried for a while to figure out what the svelte way of solving this issue, but then found that if I rewrite this code using addEventListener in the onMount function, I then get the exact effect I want, where I don't get any passive listener warnings, and I can call e.preventDefault, fixing my mobile issues. This leads me to believe that attribute events in svelte are either set to the incorrect passive settings by default, or need to bring back the feature to change the passive setting like in previous version of svelte.
Reproduction
Can be run using svelte playground here.
<script lang='ts'>
import { onMount } from 'svelte';
let canvas: HTMLCanvasElement; // set by svelte bind
let context : CanvasRenderingContext2D; // set on mount
let offset_top: number = $state(0); // set on mount
let offset_left: number = $state(0); // set on mount
let is_drawing: boolean = false;
onMount(() => {
context = canvas.getContext("2d")!;
const bounding_box = canvas.getBoundingClientRect();
offset_top = bounding_box.top;
offset_left = bounding_box.left;
})
type Pos = {x:number, y:number}
const handlestart = () => {
is_drawing = true;
};
const handlemove = (e: Event, pos: Pos) => {
if (!is_drawing) return;
e.preventDefault();
context.beginPath();
context.fillStyle = '#000';
context.arc(pos.x-5, pos.y-5, 10, 0, 360);
context.fill();
};
const handleend = () => {
is_drawing = false;
};
const calculatetouchpos = (e: TouchEvent) => {
const { clientX, clientY } = e.touches[0];
return {
x: clientX - offset_left,
y: clientY - offset_top,
};
}
</script>
<canvas
bind:this={canvas}
width=250
height=250
style:border="3px solid black"
onmousedown={(e) => handlestart()}
onmousemove={(e) => {
handlemove(e, {x: e.offsetX, y: e.offsetY})
}}
onmouseup={(e) => handleend()}
ontouchstart={(e) => { handlestart(); }}
ontouchmove={(e) => {
const pos = calculatetouchpos(e);
handlemove(e, pos);
}}
ontouchend={() => {handleend()}}
>
</canvas>The hack to fix the issue temporarily can be run in the svelte playground here.
<svelte:options runes />
<script lang='ts'>
import { onMount } from 'svelte';
let canvas: HTMLCanvasElement; // set by svelte bind
let context : CanvasRenderingContext2D; // set on mount
let offset_top: number = $state(0); // set on mount
let offset_left: number = $state(0); // set on mount
let is_drawing: boolean = false;
onMount(() => {
context = canvas.getContext("2d")!;
const bounding_box = canvas.getBoundingClientRect();
offset_top = bounding_box.top;
offset_left = bounding_box.left;
canvas.addEventListener('touchmove', (e) => {
const pos = calculatetouchpos(e);
handlemove(e, pos);
});
})
type Pos = {x:number, y:number}
const handlestart = () => {
is_drawing = true;
};
const handlemove = (e: Event, pos: Pow) => {
if (!is_drawing) return;
e.preventDefault();
context.beginPath();
context.fillStyle = '#000';
context.arc(pos.x-5, pos.y-5, 10, 0, 360);
context.fill();
};
const handleend = () => {
is_drawing = false;
};
const calculatetouchpos = (e: TouchEvent) => {
const { clientX, clientY } = e.touches[0];
return {
x: clientX - offset_left,
y: clientY - offset_top,
};
}
</script>
<canvas
bind:this={canvas}
width=250
height=250
style:border="3px solid black"
onmousedown={(e) => handlestart()}
onmousemove={(e) => {
handlemove(e, {x: e.offsetX, y: e.offsetY})
}}
onmouseup={(e) => handleend()}
ontouchstart={(e) => { handlestart(); }}
ontouchend={() => {handleend()}}
>
</canvas>Logs
Firefox warning: Ignoring “preventDefault()” call on event of type “touchmove” from a listener registered as “passive”.
Chromium warning: Unable to preventDefault inside passive event listener invocation.System Info
Firefox: 134.0.1 (64-bit)
Chrome: Version 132.0.6834.83 (Official Build) (64-bit)
Edge: Version 132.0.2957.115 (Official build) (64-bit)
Arc: Based on Chromium version 132.0.6834.84 (Official Build) (64-bit)Severity
annoyance