svelte-gestures

repository·main·Indexed 41 days ago

https://github.com/rezi/svelte-gestures

A lightweight collection of gesture recognizers for Svelte (optimized for Svelte 5) using Pointer Events for cross-platform support of mouse, touch, and stylus inputs. Version 5.2.2 implements recognizers as Svelte Attachments via the `use` directive. Includes support for pan, pinch, rotate, swipe, tap, press, multi-touch, and shape gestures, as well as a plugin system for visual and haptic feedback and a `useComposedGesture` utility for complex interaction logic.

Tokens
17.8K
Snippets
54
Records
72
Agent score
51%

What's inside svelte-gestures

  1. Extend gestures with Plugins

    main

    Plugins are side effects used to enhance gestures with visual, audio, or haptic feedback (e.g., highlighting a path or vibrating the device). Plugins do not modify the gesture logic itself.

    A plugin is a function that accepts an options object and returns an object implementing the plugin interface.

    type Plugin = (options: any) => {
        onMove: PluginEventCallback;
        onDown: PluginEventCallback;
        onUp: PluginEventCallback;
        onDestroy?: () => void;
        onInit?: (activeEvents: PointerEvent[]) => void;
    };
  2. Understand svelte-gestures API events and data structure

    main

    Most gesture recognizers in svelte-gestures trigger a main event and three lifecycle events based on the pointer state: [gesture][up], [gesture][down], and [gesture][move].

    For example, the pan action triggers pan, panup, pandown, and panmove.

    Each event dispatches event.detail containing a common set of properties. While specific properties vary by gesture, they generally include:

    • event: The native PointerEvent.
    • pointersCount: The number of active pointers.
    • target: The HTMLElement where the gesture started.
    • x, y: Coordinates within the gesture element.
    • pointerType: 'touch' | 'mouse' | 'pen'.
    <script lang="ts">
      import { pan, type PanCustomEvent, type GestureCustomEvent } from 'svelte-gestures';
      let x: number;
      let y: number;
      let target: EventTarget;
    
      function handler(event: PanCustomEvent) { 
        x = event.detail.x;
        y = event.detail.y;
        target = event.detail.target;
      }
    
      function panDown(gestureEvent: GestureCustomEvent) { 
        const { event, pointersCount, target, x, y } = gestureEvent.detail;
      }
    
      function panMove(gestureEvent: GestureCustomEvent) { 
        console.log(gestureEvent.detail);
      }
    </script>
    
    <div
      use:pan
      on:pan={handler}
      on:pandown={panDown}
      on:panmove={panMove}
      style="width:500px;height:500px;border:1px solid black;"
    ></div>
  3. How gesture attachments work in svelte-gestures

    main

    In version 5.x, recognizers are implemented as Svelte Attachments. They are used with the use directive (e.g., usePan) and act as custom event emitters.

    Each gesture attachment typically accepts four arguments:

    1. Main Handler: A function triggered when the primary gesture is recognized.
    2. Options Function: A function that returns an options object. Wrapping this in a function allows you to change gesture parameters dynamically during the component lifecycle.
    3. Extra Event Handlers: An object containing handlers for sub-events (e.g., onpanup, onpandown, onpanmove).
    4. isRaw (boolean): If true, the attachment returns the raw gesture function and event handlers instead of a standard Svelte attachment object. This is useful for using the @attach syntax on elements like <svelte:body> or for programmatic addEventListener usage.
    <script lang="ts">
    	import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures';
    	
    	function handler(event: SwipeCustomEvent) {
    		console.log(event.detail.direction);
    	}
    </script>
    
    <section
    	{...useSwipe(handler, () => ({ timeframe: 300, minSwipeDistance: 50, touchAction: 'none' }))}>
    		<div>Swipe me</div>
    </section>
  4. How composedGesture works

    main

    The composedGesture action is used to compose multiple gestures together or switch between gestures dynamically during a single interaction. It does not listen to gestures itself but acts as a controller.

    To use it, pass a function with the signature: (register: RegisterGestureType) => (activeEvents: PointerEvent[], event: PointerEvent) => boolean

    Inside this function, you use the register callback to add gestures. register returns an object containing onDown, onMove, and onUp callbacks. While onDown and onUp are managed by composedGesture, you must manually trigger the onMove callbacks within the returned function to implement custom logic (e.g., switching from scrolling to panning based on a press).

    // Example signature for the composedGesture function
    const myComposedGesture: GestureCallback = (register: RegisterGestureType) => {
      const gestureA = register(gestureA_Fn, optionsA);
      const gestureB = register(gestureB_Fn, optionsB);
    
      return (activeEvents: PointerEvent[], event: PointerEvent) => {
        // Logic to decide which gesture's move handler to run
        if (condition) {
          gestureA.onMove(activeEvents, event);
        } else {
          gestureB.onMove(activeEvents, event);
        }
      };
    };
  5. How composed gestures work

    main

    A composedGesture does not listen to gestures itself; instead, it acts as a container to compose multiple gestures or switch between them during a single interaction. This is useful for complex logic, such as distinguishing between a 'scroll' and a 'pan' based on whether a 'press' was initially triggered.

    To use it, pass a function to useComposedGesture with the signature: (register: RegisterFnType) => (activeEvents: PointerEvent[], event: PointerEvent) => boolean.

    Inside the function, you use register(gesture, options) to add gestures. Each registration returns an object with onDown, onMove, and onUp. You must return a callback from your function that explicitly triggers the onMove methods of the registered gestures.

    useComposedGesture((register) => {
      const pan = register(pan, { /* options */ });
      const press = register(press, { /* options */ });
    
      return (activeEvents, event) => {
        // Logic to decide which onMove to call
        if (press.isTriggered) {
          return pan.onMove(activeEvents, event);
        }
        return false;
      };
    });
  6. Install svelte-gestures

    main

    Install svelte-gestures using your preferred package manager.

    Note on Svelte versions:

    • Use version 4 for Svelte 4 projects.
    • Use version 1.5.2 or lower for older Svelte projects.
    npm install svelte-gestures
    
    # Or via JSR
    npx jsr add @rezi/svelte-gestures
    
    # Or via Deno
    deno add @rezi/svelte-gestures
  7. Configure TypeScript types for Svelte language tools

    main

    If you use Svelte language tools (like Svelte for VS Code) and want to see return types for gesture actions (e.g., <div on:swipe={fn}>) in your markup on Svelte versions 4.2 or below, add the following reference to your global.d.ts or similar definition file:

    /// <reference types="svelte-gestures" />

    If you are using an older version of SvelteKit, add this line after your existing @sveltejs/kit reference:

    /// <reference types="@sveltejs/kit" />

    If global.d.ts does not exist and you are on Svelte 4.1 or below, create the file and ensure it is included in your tsconfig.json under the include: [] array.

    /// <reference types="svelte-gestures" />
  8. Swipe event directions

    main

    The swipe gesture recognizes four cardinal directions based on the movement vector. A swipe is only triggered if the movement is significantly dominant in one axis (the primary axis must be at least twice the distance of the secondary axis) and exceeds the minSwipeDistance.

    Valid direction values:

    • 'top'
    • 'bottom'
    • 'left'
    • 'right'
    • null (if no direction is determined)
  9. Use the isRaw option for @attach syntax

    main

    When you need to attach gestures to elements that do not support spread properties (like <svelte:body>), set the fourth argument of the gesture function to true. This returns an object containing the gesture attachment and the specific sub-event handlers.

    <script lang="ts">
    ...
    
    const { swipe, onswipe, onswipedown, onswipemove, onswipeup } = useSwipe(
      handler,
      () => ({ timeframe: 300, minSwipeDistance: 50, touchAction: 'none' }),
      {
        onswipemove: moveHandler,
        onswipeup: upHandler,
        onswipedown: downHandler
      },
      true
    );
    </script>
    
    <svelte:body {@attach swipe} {onswipe} {onswipedown} {onswipemove} {onswipeup} />
  10. Configure the Tap gesture (useTap)

    main

    The useTap attachment triggers when a pointer is released within a specific timeframe.

    Event Detail:

    • x: X coordinate.
    • y: Y coordinate.
    • target: HTMLElement.
    • pointerType: 'touch' | 'mouse' | 'pen'.

    Options:

    • timeframe (default: 300ms)
    • touchAction (default: 'auto')
    • composed: Only for composedGesture.
    • plugins: Array of plugins.
  11. Use the Swipe gesture

    main

    The swipe action (triggered via on:swipe) fires if a pointer moves a minimum distance within a specific timeframe in a single direction.

    Options:

    • timeframe (default: 300ms): The maximum time allowed for the swipe.
    • minSwipeDistance (default: 60px): The minimum distance required to trigger the swipe.
    • touchAction (default: 'none'): CSS touch-action style.
    • composed: Only applicable when used inside a composedGesture.

    Event Detail Properties:

    • direction: 'top' | 'right' | 'bottom' | 'left'.
    • target: The HTMLElement recorded when the swipe starts.
    • pointerType: 'touch' | 'mouse' | 'pen'.
    <script lang="ts">
    import { swipe, type SwipeCustomEvent } from 'svelte-gestures';
    let direction, target, pointerType;
    
    function handler(event: SwipeCustomEvent) {
      direction = event.detail.direction;
      target = event.detail.target;
      pointerType = event.detail.pointerType;
    }
    </script>
    
    <div 
      use:swipe={{ timeframe: 300, minSwipeDistance: 60 }} 
      on:swipe={handler} 
      style="width:500px;height:500px;border:1px solid black;"
    >
      direction: {direction}
    </div>
  12. Use the `usePress` attachment

    main

    The usePress attachment triggers a press event when a pointer is held down for a specific duration. By default, the event fires when the pointer is released after the timeframe has passed. If triggerBeforeFinished is set to true, the event fires immediately once the timeframe is reached, even if the user is still pressing.

    // Example of usePress options
    usePress({
      triggerBeforeFinished: true,
      timeframe: 300,
      spread: 4,
      touchAction: 'none'
    });