# Pointeract
Pointeract is a lightweight, modular JavaScript/TypeScript library for handling user interactions with DOM elements. It provides robust gesture detection for multitouch, touchpad, and mouse interactions with a core bundle size of only 1KB minified + gzipped. Built on the SynthKernel architecture, it offers a highly modular, extensible, and efficient approach to interaction handling.
The library excels at complex gestures where most interaction libraries fail, supporting mouse, mouse wheel, touch, and touchpad input. It is fully tree-shakable, meaning the fewer modules you use, the smaller your bundle becomes. Pointeract supports all modern browsers (IE11+) and works with any JavaScript framework through its ESM module format.
## Installation
Install Pointeract via npm, pnpm, yarn, or bun, or include it directly from a CDN.
```bash
# npm
npm add pointeract
# pnpm
pnpm add pointeract
# yarn
yarn add pointeract
# bun
bun add pointeract
```
```html
```
## Pointeract Class - Core Instance
The main orchestrator class that manages modules, events, and lifecycle. Pass a DOM element and optional modules to create an instance.
```typescript
import { Pointeract, Drag, PreventDefault } from 'pointeract';
// Get a DOM element to attach interactions to
const app = document.getElementById('app') as HTMLDivElement;
// Define options with coordinate output format
const options = {
element: app,
coordinateOutput: 'relative', // 'absolute' | 'relative' | 'relativeFraction'
};
// Create instance with modules
const pointeract = new Pointeract(options, [Drag, PreventDefault]);
// Start monitoring interactions
pointeract.start();
// Subscribe to drag events
pointeract.on('drag', (e) => {
console.log(`Dragged to (${e.x}, ${e.y}) with delta (${e.deltaX}, ${e.deltaY})`);
});
// Output: Dragged to (150, 200) with delta (5, 3)
```
## on() - Subscribe to Events
Subscribe to interaction events with full TypeScript support. Returns the instance for method chaining.
```typescript
import { Pointeract, Drag, Click, type Events } from 'pointeract';
const pointeract = new Pointeract({ element: app }, [Drag, Click]).start();
// Type-safe event handler
const dragHandler = (e: Events<[typeof Drag]>['drag']) => {
console.log(`Position: (${e.x}, ${e.y})`);
console.log(`Delta: (${e.deltaX}, ${e.deltaY})`);
};
// Chain multiple subscriptions
pointeract
.on('drag', dragHandler)
.on('trueClick', (e) => {
console.log(`Clicked at (${e.x}, ${e.y}), streak: ${e.streak}`);
if (e.streak === 2) console.log('Double click detected!');
});
```
## off() - Unsubscribe from Events
Remove event listeners by passing the same handler function reference.
```typescript
import { Pointeract, Drag, type Events } from 'pointeract';
const pointeract = new Pointeract({ element: app }, [Drag]).start();
const handler = (e: Events<[typeof Drag]>['drag']) => {
console.log(`Drag event: (${e.x}, ${e.y})`);
};
// Subscribe
pointeract.on('drag', handler);
// Later, unsubscribe
pointeract.off('drag', handler);
```
## start() and stop() - Lifecycle Control
Control the instance and individual module lifecycles. Stopping pauses event monitoring; starting resumes it.
```typescript
import { Pointeract, Drag, PreventDefault } from 'pointeract';
const pointeract = new Pointeract({ element: app }, [Drag, PreventDefault]);
// Start monitoring
pointeract.start();
// Pause all interactions
pointeract.stop();
// Resume monitoring
pointeract.start();
// Stop specific modules only (PreventDefault disabled, Drag still active)
pointeract.stop([PreventDefault]);
// Re-enable specific modules
pointeract.start([PreventDefault]);
```
## dispose() - Cleanup Resources
Completely dispose of the Pointeract instance, removing all listeners and cleaning up resources.
```typescript
import { Pointeract, Drag } from 'pointeract';
const pointeract = new Pointeract({ element: app }, [Drag])
.start()
.on('drag', (e) => console.log(e));
// When done, dispose completely (no need to call stop() or off() first)
pointeract.dispose();
```
## Drag Module
Handles single-pointer drag interactions. Dispatches `drag` events when mouse or touch is pressed and moved.
```typescript
import { Pointeract, Drag } from 'pointeract';
const app = document.getElementById('canvas') as HTMLDivElement;
let offsetX = 0;
let offsetY = 0;
const pointeract = new Pointeract({ element: app }, [Drag])
.start()
.on('drag', (e) => {
// e.deltaX, e.deltaY: movement since last event
// e.x, e.y: current pointer position
offsetX += e.deltaX;
offsetY += e.deltaY;
app.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
});
```
## Click Module
Detects true clicks by tracking pointer movement. Only fires when movement is below threshold, unlike native click events.
```typescript
import { Pointeract, Click } from 'pointeract';
const pointeract = new Pointeract({
element: app,
clickStreakWindow: 400, // ms between clicks for streak detection
clickMoveThreshold: 5, // max px movement to count as click
}, [Click])
.start()
.on('trueClick', (e) => {
console.log(`Click at (${e.x}, ${e.y})`);
console.log(`Target: ${e.target}`);
// Detect multi-clicks
if (e.streak === 1) console.log('Single click');
if (e.streak === 2) console.log('Double click');
if (e.streak === 3) console.log('Triple click');
});
```
## Swipe Module
Detects swipe gestures in configurable directions with velocity and streak tracking.
```typescript
import { Pointeract, Swipe } from 'pointeract';
const pointeract = new Pointeract({
element: app,
swipeMinDistance: 20, // minimum px to trigger swipe
swipeMinVelocity: 0, // minimum px/s velocity
swipeStreakWindow: 400, // ms for grouping multi-finger swipes
}, [Swipe])
.start()
.on('swipe', (e) => {
console.log(`Swipe ${e.direction}`); // 'left' | 'right' | 'up' | 'down'
console.log(`Velocity: ${e.velocity} px/ms`);
console.log(`Angle: ${e.angle} radians`);
console.log(`Duration: ${e.duration} ms`);
console.log(`Fingers: ${e.streak}`); // multi-finger swipe count
});
// Custom 8-direction swipe detection
const eightDirectionMap = {
left: -(Math.PI / 8) * 7,
'down-left': -(Math.PI / 8) * 5,
down: -(Math.PI / 8) * 3,
'down-right': -Math.PI / 8,
right: Math.PI / 8,
'up-right': (Math.PI / 8) * 3,
up: (Math.PI / 8) * 5,
'up-left': (Math.PI / 8) * 7,
};
new Pointeract({
element: app,
swipeDirectionMap: eightDirectionMap,
}, [Swipe]).start();
```
## WheelPanZoom Module
Handles mouse wheel and touchpad gestures for pan and zoom interactions with automatic control schema detection.
```typescript
import { Pointeract, WheelPanZoom } from 'pointeract';
let scale = 1;
let panX = 0;
let panY = 0;
const pointeract = new Pointeract({
element: app,
proControlSchema: false, // true: ctrl+wheel=zoom, shift+wheel=pan-x, wheel=pan-y
lockControlSchema: false, // prevent auto-detection
zoomFactor: 0.1, // zoom speed multiplier
}, [WheelPanZoom])
.start()
.on('zoom', (e) => {
// e.factor < 1: zoom out, e.factor > 1: zoom in
// e.x, e.y: zoom origin coordinates
scale *= e.factor;
console.log(`Zoom to ${scale}x at (${e.x}, ${e.y})`);
})
.on('pan', (e) => {
panX += e.deltaX;
panY += e.deltaY;
console.log(`Pan to (${panX}, ${panY})`);
});
```
## MultitouchPanZoom Module
Handles two-finger touch gestures for pan (midpoint movement) and pinch-to-zoom (distance change).
```typescript
import { Pointeract, MultitouchPanZoom } from 'pointeract';
let scale = 1;
let panX = 0;
let panY = 0;
const pointeract = new Pointeract({ element: app }, [MultitouchPanZoom])
.start()
.on('pan', (e) => {
// Triggered when midpoint between two touches moves
panX += e.deltaX;
panY += e.deltaY;
app.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;
})
.on('zoom', (e) => {
// Triggered when distance between two touches changes
scale *= e.factor;
app.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;
});
```
## PreventDefault Module
Disables default browser behaviors for touch/mouse/wheel events on the target element.
```typescript
import { Pointeract, Drag, PreventDefault } from 'pointeract';
// Prevent browser scroll, text selection, and other defaults
const pointeract = new Pointeract({ element: app }, [Drag, PreventDefault])
.start()
.on('drag', (e) => {
// Drag without triggering page scroll or text selection
console.log(`Drag: (${e.deltaX}, ${e.deltaY})`);
});
// Temporarily allow default behavior
pointeract.stop([PreventDefault]);
// Re-enable prevention
pointeract.start([PreventDefault]);
```
## Lubricator Module - Smooth Animations
Intercepts and smoothifies events through interpolation for fluid animations.
```typescript
import {
Pointeract,
WheelPanZoom,
Drag,
Lubricator,
panPreset as pan,
zoomPreset as zoom,
dragPreset as drag
} from 'pointeract';
// Using presets for quick configuration
const pointeract = new Pointeract({
element: app,
lubricator: { pan, zoom, drag },
}, [WheelPanZoom, Drag, Lubricator])
.start()
.on('zoom', (e) => {
// Smooth, interpolated zoom events
console.log(`Smooth zoom factor: ${e.factor}`);
});
// Custom lubricator configuration
new Pointeract({
element: app,
lubricator: {
zoom: {
decayFactor: 0.25, // lower = smoother (more lag)
fields: {
factor: {
countType: 'product', // aggregate product equals raw
diminishBoundary: 0.01, // stop when delta < 0.01
},
},
},
pan: {
decayFactor: 0.3,
fields: {
deltaX: { countType: 'sum', diminishBoundary: 0.5 },
deltaY: { countType: 'sum', diminishBoundary: 0.5 },
},
},
},
}, [WheelPanZoom, Lubricator]).start();
```
## Options - Reactive Configuration
Options are passed by reference and can be updated reactively at runtime.
```typescript
import { Pointeract, WheelPanZoom, type Options } from 'pointeract';
const options: Options<[typeof WheelPanZoom]> = {
element: app,
coordinateOutput: 'absolute',
zoomFactor: 0.1,
};
const pointeract = new Pointeract(options, [WheelPanZoom]).start();
// Reactively update options at runtime
options.coordinateOutput = 'relative'; // immediately takes effect
options.zoomFactor = 0.2; // zoom speed changes instantly
```
## TypeScript Types - Full Type Safety
Access orchestrated types for options, events, and instance interfaces.
```typescript
import {
Pointeract,
Drag,
Click,
type Options,
type Events,
type PointeractInterface
} from 'pointeract';
// Type-safe options
type MyOptions = Options<[typeof Drag, typeof Click]>;
const options: MyOptions = {
element: app,
coordinateOutput: 'relative',
clickStreakWindow: 400,
};
// Type-safe event handlers
type MyEvents = Events<[typeof Drag, typeof Click]>;
const onDrag = (e: MyEvents['drag']) => console.log(e.x, e.y);
const onClick = (e: MyEvents['trueClick']) => console.log(e.streak);
// Type-safe instance
type MyInstance = PointeractInterface<[typeof Drag, typeof Click]>;
const instance: MyInstance = new Pointeract(options, [Drag, Click]);
```
## Custom Modules - Extending Pointeract
Create custom modules by extending BaseModule with hooks for pointer and wheel events.
```typescript
import { BaseModule, BaseOptions, StdEvents, type BaseArgs } from 'pointeract';
// Define custom options
interface CustomOptions extends BaseOptions {
holdThreshold?: number;
}
// Define custom events
interface CustomEvents extends StdEvents {
hold: { x: number; y: number; duration: number };
}
// Define augmentation (methods added to Pointeract instance)
interface CustomAugmentation {
getHoldCount: () => number;
}
class HoldModule extends BaseModule {
private holdCount = 0;
private startTime = 0;
constructor(...args: BaseArgs) {
super(...args);
this.augment({ getHoldCount: () => this.holdCount });
}
onPointerDown = (e: PointerEvent) => {
this.startTime = Date.now();
};
onPointerUp = (e: PointerEvent, pointer: any) => {
const duration = Date.now() - this.startTime;
const threshold = this.options.holdThreshold ?? 500;
if (duration >= threshold) {
this.holdCount++;
const coords = this.toTargetCoords({ x: e.clientX, y: e.clientY });
this.dispatch('hold', { x: coords.x, y: coords.y, duration });
}
};
dispose = () => {
this.holdCount = 0;
};
}
// Usage
const pointeract = new Pointeract({
element: app,
holdThreshold: 1000,
}, [HoldModule])
.start()
.on('hold', (e) => console.log(`Held for ${e.duration}ms at (${e.x}, ${e.y})`));
// Access augmented method
console.log(`Total holds: ${pointeract.getHoldCount()}`);
```
## Full Application Example
Complete example combining multiple modules for a canvas-like interaction experience.
```typescript
import {
Pointeract,
Drag,
Click,
WheelPanZoom,
MultitouchPanZoom,
PreventDefault,
Lubricator,
panPreset as pan,
zoomPreset as zoom,
type Events
} from 'pointeract';
const canvas = document.getElementById('canvas') as HTMLDivElement;
// State
let scale = 1;
let panX = 0;
let panY = 0;
// Options with smooth animations
const options = {
element: canvas,
coordinateOutput: 'relative' as const,
proControlSchema: true,
lubricator: { pan, zoom },
};
// Create instance with all modules
const pointeract = new Pointeract(options, [
Drag,
Click,
WheelPanZoom,
MultitouchPanZoom,
PreventDefault,
Lubricator,
])
.start()
.on('pan', (e) => {
panX += e.deltaX;
panY += e.deltaY;
updateTransform();
})
.on('zoom', (e) => {
scale *= e.factor;
scale = Math.max(0.1, Math.min(10, scale)); // clamp scale
updateTransform();
})
.on('trueClick', (e) => {
if (e.streak === 2) {
// Double-click to reset
scale = 1;
panX = 0;
panY = 0;
updateTransform();
}
});
function updateTransform() {
canvas.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;
}
// Cleanup on page unload
window.addEventListener('beforeunload', () => pointeract.dispose());
```
## Summary
Pointeract provides a comprehensive solution for handling complex user interactions in web applications. Its modular architecture allows developers to include only the functionality they need, keeping bundle sizes minimal. The library is ideal for building canvas applications, image viewers, map interfaces, interactive diagrams, and any UI requiring sophisticated gesture handling. Key use cases include drag-and-drop interfaces, pinch-to-zoom galleries, swipe-based navigation, and smooth pan/zoom experiences.
Integration with existing projects is straightforward through the standard module pattern. Pointeract works seamlessly with React, Vue, Angular, Svelte, and vanilla JavaScript applications. The reactive options system enables dynamic configuration changes, while the event-based API integrates naturally with state management solutions. For advanced use cases, the custom module API allows developers to extend functionality while maintaining full TypeScript support and the library's performance characteristics.