UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

112 lines (111 loc) 4.72 kB
// Time in milliseconds to keep trackpad detection state const TRACKPAD_DETECTION_STATE_TIMEOUT = 60000; // 1 minute /** * Creates a trackpad detection function that distinguishes between trackpad and mouse wheel events. * * This factory function returns a detector that analyzes WheelEvent characteristics to determine * the input device type. The detection is based on several behavioral patterns: * * - **Pinch-to-zoom gestures**: Trackpads generate wheel events with modifier keys (Ctrl/Meta) * and continuous (non-integer) delta values * - **Horizontal scrolling**: Trackpads naturally produce horizontal scroll events (deltaX), * while mice typically only scroll vertically * - **Continuous scrolling**: Trackpad scroll deltas are usually fractional values, while mouse * wheels produce discrete integer values * * The detector maintains state across events to provide consistent results during a scroll session. * Once a trackpad is detected, the state persists for 60 seconds before resetting. * * @returns A detection function that accepts WheelEvent and optional devicePixelRatio * * @example * ```typescript * const isTrackpad = isTrackpadDetector(); * * element.addEventListener('wheel', (e) => { * if (isTrackpad(e)) { * console.log('Trackpad scroll detected'); * } else { * console.log('Mouse wheel detected'); * } * }); * ``` */ function isTrackpadDetector() { let isTrackpadDetected = false; let cleanStateTimer = null; /** * Marks the current input device as trackpad and resets the state timeout. * This ensures consistent detection during continuous scroll operations. */ const markAsTrackpad = () => { isTrackpadDetected = true; clearTimeout(cleanStateTimer); cleanStateTimer = setTimeout(() => { isTrackpadDetected = false; }, TRACKPAD_DETECTION_STATE_TIMEOUT); }; /** * Analyzes a wheel event to determine if it originated from a trackpad. * * @param e - The WheelEvent to analyze * @param dpr - Device pixel ratio for normalizing delta values. Defaults to window.devicePixelRatio. * This normalization accounts for browser zoom levels to improve detection accuracy. * @returns `true` if the event is from a trackpad, `false` if from a mouse wheel */ return (e, dpr = globalThis.devicePixelRatio || 1) => { const normalizedDeltaY = e.deltaY * dpr; const normalizedDeltaX = e.deltaX * dpr; const hasFractionalDelta = normalizedDeltaY && !Number.isInteger(normalizedDeltaY); // Detection 1: Pinch-to-zoom gesture // Trackpad pinch-to-zoom generates wheel events with ctrlKey or metaKey. // Combined with non-integer deltaY, this is a strong indicator of trackpad. const isPinchToZoomGesture = (e.ctrlKey || e.metaKey) && hasFractionalDelta; if (isPinchToZoomGesture) { markAsTrackpad(); return true; } // Detection 2: Horizontal scroll (deltaX) // Trackpad naturally produces horizontal scroll events. // Note: When Shift is pressed, browser swaps deltaX and deltaY for mouse wheel, // so we skip this check to avoid false positives. const hasHorizontalScroll = normalizedDeltaX !== 0; const isShiftPressed = e.shiftKey; if (hasHorizontalScroll && !isShiftPressed) { markAsTrackpad(); return true; } // Detection 3: Fractional deltaY (mouse produces integer values) // If we have non-integer deltaY without ctrl/meta keys, it's likely NOT trackpad // (could be browser zoom or other factors), so we explicitly return false. if (hasFractionalDelta) { return false; } // Fallback: Return previously detected state // This helps maintain consistency across rapid scroll events. return isTrackpadDetected; }; } /** * Global trackpad detector instance for wheel events. * * Use this pre-configured detector to check if a wheel event originated from a trackpad * rather than a traditional mouse wheel. The detector maintains state across calls to provide * consistent results throughout a scroll session. * * @example * ```typescript * import { isTrackpadWheelEvent } from './utils/functions'; * * canvas.addEventListener('wheel', (event) => { * if (isTrackpadWheelEvent(event)) { * // Handle smooth trackpad scrolling * applyMomentumScrolling(event); * } else { * // Handle discrete mouse wheel steps * applySteppedScrolling(event); * } * }); * ``` */ export const isTrackpadWheelEvent = isTrackpadDetector();