-
Notifications
You must be signed in to change notification settings - Fork 621
Description
Overview
Add scroll wheel support to SkiaSharp's Blazor WASM views (SKCanvasView and SKGLView) with values normalized to the v120 standard (120 = one discrete mouse wheel notch).
Parent issue: #3533
Platform Details
Native API
The browser exposes wheel events via the WheelEvent interface with two key properties:
WheelEvent.deltaY— the scroll amount (double)WheelEvent.deltaMode— the unit of the delta value:0(DOM_DELTA_PIXEL) — value is in CSS pixels1(DOM_DELTA_LINE) — value is in lines2(DOM_DELTA_PAGE) — value is in pages
Raw Values Per Discrete Mouse Notch
⚠️ Important: The W3C spec explicitly states: "authors can not assume a given rotation amount will produce the same delta value in all user agents" and that "precise measurement is specific to device, operating system, and application configurations" — W3C Pointer Events 4. The values below are observed defaults and may vary.
| Browser | OS | deltaMode | Per notch value | Source |
|---|---|---|---|---|
| Chrome/Edge | Windows | 0 (pixel) | ~100 | Chromium default |
| Chrome/Edge | macOS | 0 (pixel) | ~100 (varies by device) | Device-dependent |
| Safari | macOS | 0 (pixel) | ~100 | Device-dependent (measured same as Chrome on macOS) |
| Firefox | Windows/macOS | 1 (line) | ~3 | Default for mouse; trackpads use pixel mode (0) |
| Firefox | Windows/macOS | 0 (pixel) | ~100 | When dom.event.wheel-deltaMode-lines.disabled=true (non-default) |
| Firefox | Linux | 1 (line) | ~3 | Default on Linux |
Trackpad / Fine Scroll Values
| Device | deltaMode | Typical values |
|---|---|---|
| Trackpad (any browser) | 0 (pixel) | ±1 to ±10 per event (fractional movement) |
| Magic Mouse | 0 (pixel) | ±1 to ±10 (similar to trackpad) |
| High-res mouse | 0 (pixel) | sub-100 values at higher frequency |
Sign Convention
deltaY > 0= scroll down (toward user)deltaY < 0= scroll up (away from user)- Must negate to match v120 standard (positive = up)
Important Notes
- The "16px per line" sometimes referenced in browser internals is a de-facto convention for rendering, NOT tied to CSS
font-sizeorline-height. Our normalization does not use this value — we convert lines directly to v120 units. deltaMode=2(page) is rare in practice — mainly from some accessibility tools.- All major pixel-mode browsers (Chrome, Edge, Safari) converge on ~100 CSS pixels per notch. The original estimate of Safari at ~120px was incorrect — Safari reports ~100px, same as Chromium-based browsers.
deltaModeMUST be checked — the samedeltaYvalue means completely different things in pixel vs line vs page mode.
Official Documentation
- WheelEvent.deltaY (MDN) — "the scroll amount in the vertical direction"
- WheelEvent.deltaMode (MDN) — "the unit of the delta values"
- WheelEvent interface (MDN) — full interface reference
- W3C Pointer Events 4: Wheel Events — current authoritative W3C specification (wheel events were moved from UI Events to Pointer Events)
- W3C UI Events (historical) — §1.1.1 notes: "The Mouse Events and Wheel Events section of this specification have been moved to the Pointer Events specification [pointerevents4]."
Normalization Logic
// Normalize browser wheel delta to v120 standard (120 = one discrete notch).
// Sign: positive = scroll up (away from user), matching Windows convention.
//
// NOTE: Per-notch pixel values are browser/OS/device dependent.
// These constants represent the most common observed defaults.
// The W3C spec explicitly states authors cannot assume consistent
// delta values across user agents.
const WHEEL_DELTA = 120;
const PIXELS_PER_NOTCH = 100; // Chrome/Edge/Safari de-facto default (~100 CSS px per notch)
const LINES_PER_NOTCH = 3; // Firefox default (follows OS 'lines per notch' setting; 3 is Windows/macOS default)
let delta: number;
if (e.deltaMode === WheelEvent.DOM_DELTA_LINE) {
// Firefox line mode: ~3 lines per notch → 3 × 40 = 120
delta = Math.round(-e.deltaY * (WHEEL_DELTA / LINES_PER_NOTCH));
} else if (e.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
// Rare: ~1 page per notch → 1 × 120 = 120
delta = Math.round(-e.deltaY * WHEEL_DELTA);
} else {
// DOM_DELTA_PIXEL (default, most common)
// Chrome: ~100px per notch → 100 × 1.2 = 120
// Trackpad: 5px → 6, 1px → 1 (no dead zone)
delta = Math.round(-e.deltaY * (WHEEL_DELTA / PIXELS_PER_NOTCH));
}Expected Results
| Input | Calculation | WheelDelta |
|---|---|---|
| Chrome mouse notch (100px) | round(-(-100) × 1.2) |
120 |
| Firefox mouse notch (3 lines) | round(-(-3) × 40) |
120 |
| Safari mouse notch (100px) | round(-(-100) × 1.2) |
120 |
| Trackpad 5px down | round(-(5) × 1.2) |
-6 |
| Trackpad 1px up | round(-(-1) × 1.2) |
1 |
| Page mode 1 page | round(-(-1) × 120) |
120 |
| Horizontal only (deltaY=0) | round(0 × anything) |
0 |
Implementation Notes
- Listen for
wheelevent on the canvas element (alongside existing pointer events) - Fire
SKTouchAction.WheelChangedwithSKTouchDeviceType.Mouse - Pointer coordinates from
e.clientX/Y - rect.left/top(same as pointer events) wheelDeltaisintin C# —Math.round()ensures integer on JS side- Must handle both NET7+ (
[JSImport]/[JSExport]) and pre-NET7 (DotNetObjectReference.invokeMethod) interop paths - When
Handledis returned true from C#, calle.preventDefault()ande.stopPropagation()to prevent browser default scroll behavior - Both
SKCanvasViewandSKGLViewneed wheel support - Update
SKTouchInterop.tsANDSKTouchInterop.js(no TypeScript compiler in repo — JS is hand-maintained)
Implementation Status
The v120 normalization has been implemented on branch copilot/add-touch-event-blazor-canvas (commit 052d325bc).
Validated with Playwright across 3 browser engines:
- Chromium: 24/24 tests passing
- Firefox: 24/24 tests passing
- WebKit (Safari): 24/24 tests passing
Key correction from original analysis
Safari on macOS reports ~100 CSS pixels per notch (same as Chrome/Edge), NOT ~120 as originally estimated. This was confirmed via web research and aligns with the Chromium source (kPixelsPerLineStep = 100). This means PIXELS_PER_NOTCH = 100 produces correct v120 values for ALL major pixel-mode browsers.
Research sources used for validation
- Chromium
kPixelsPerLineStepsource — 100 pixels per line step - Chromium DPI fix (bug 40646299) — deltaY now in CSS pixels (fixed Aug 2023)
- MDN WheelEvent.deltaMode — deltaMode values 0/1/2
- W3C Pointer Events 4 — authoritative wheel events spec
- Firefox line mode behavior — deltaMode=1 with OS-dependent line count
Metadata
Metadata
Assignees
Labels
Type
Projects
Status