JSPM

onwave

0.3.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 128
  • Score
    100M100P100Q65993F
  • License MIT

Zero-dependency wave gesture for the browser. onwave is to onclick what a wave is to a tap.

Package Exports

  • onwave
  • onwave/react

Readme

onwave

onwave is to onclick what a wave is to a tap.

Zero-dependency, no-ML wave gesture for the browser. Swipe your hand past the camera — that's the click.

Install

npm install onwave

Quick start

Vanilla JS

import { createWaveDetector } from 'onwave'

const detector = createWaveDetector({
  onWave: () => timer.toggle(),
})

// must be called inside a user gesture (e.g. button click)
await detector.start()

React

import { useOnWave } from 'onwave/react'

function BrewTimer() {
  const { ref, state } = useOnWave(() => timer.toggle())

  return <button ref={ref}>Wave to start — {state}</button>
}

How it works

onwave compares consecutive camera frames on a small offscreen canvas — no ML, just pixel math. The default algorithm (Eclipse) computes the mean absolute difference (MAD) between frames and its spatial spread across vertical strips of the frame. A hand swiping past the camera produces a sharp, spatially non-uniform MAD spike that neither gradual lighting changes nor AGC adjustments can replicate. When the spike clears both thresholds, the wave callback fires.

Calibration runs for ~700ms on startup to measure the ambient noise floor and set thresholds relative to it, so the detector adapts automatically to different environments (phone on a stand, flat on a table, screen on or dimmed).

API

createWaveDetector(options, algorithm?)

Option Type Default Description
onWave () => void required Fires once per wave
cooldown number 1000 ms before next wave accepted
sampleInterval number 35 ms between samples
facingMode 'user' | 'environment' 'user' Camera to use
onError (error: Error) => void Camera permission denied etc.
onStateChange (state: DetectorState) => void State change callback
onDiagnostics (data: Record<string, number>) => void Per-tick signal values

The optional algorithm argument accepts any DetectionAlgorithm. Defaults to createEclipseAlgorithm().

Returns { start, stop, calibrate, state }.

createEclipseAlgorithm(options?) — default

Frame-differencing detector. Fires on any directional swipe through the camera. Works on a stand or flat on a table without per-device tuning.

Option Type Default Description
motionThreshold number auto Min frame MAD to qualify as a swipe. Auto-calibrated from ambient noise if not set.
spatialThreshold number auto Min MAD spread across strips. Auto-calibrated if not set.
canvasSize number 16 Canvas width/height in px
numStrips number 5 Vertical strips for spatial spread check
calibrationFrames number 20 Frames sampled at startup to measure noise floor

createLumaAlgorithm(options?) — alternative

Original brightness-based detector. Fires when average frame luma drops below a calibrated baseline. Simpler but sensitive to AGC and lighting drift.

Option Type Default Description
threshold number 40 Luma drop (0–255) to trigger
sampleSize number 10 Canvas size in px
calibrationFrames number 10 Frames to establish baseline

useOnWave(onWave, options?)

React hook. Starts detector on mount, stops on unmount. Returns { ref, state, calibrate }.

Privacy

onwave processes all camera data locally in the browser. No frames, pixels, or any derived data are ever sent to a server or third party. The camera stream is released immediately when detector.stop() is called.

Browser support

Requires getUserMedia (all modern browsers) and HTTPS in production. On iOS, start() must be called inside a user gesture handler.

License

MIT