Zoom plugin

The Zoom plugin lets users zoom into the waveform by scrolling the mouse wheel (or pinching on touch screens). You can also zoom programmatically with ws.zoom() at any time — no plugin required for that.


Two ways to zoom

Programmatic zoom with ws.zoom()

Call ws.zoom(minPxPerSec) directly on any WaveSurfer instance. The argument is the desired number of pixels per second of audio:

// Zoom in to 200 px/s
ws.zoom(200)

// Zoom back out to fit the container (pass 0 or omit — see note below)
ws.zoom(0)

Audio must be loaded before calling ws.zoom() — it throws if no audio is decoded yet.

See a minimal demo: zoom example.

Scroll-wheel zoom with the Zoom plugin

The Zoom plugin hooks into the container’s wheel (and touchmove) events so users can zoom interactively:

import WaveSurfer from 'wavesurfer.js'
import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom.esm.js'

const ws = WaveSurfer.create({
  container: '#waveform',
  url: '/audio.mp3',
  plugins: [
    ZoomPlugin.create({
      scale: 0.5,
      maxZoom: 1000,
    }),
  ],
})

See it in action: Zoom plugin example.


Plugin options

Option Type Default Description
scale number 0.5 How many px/s to add or subtract per wheel step. 0.5 means each scroll tick changes the zoom by half the accumulated delta. Ignored when exponentialZooming is true.
maxZoom number container width (px) Maximum allowed pixels-per-second value. The waveform will not zoom beyond this point. Defaults to the container’s clientWidth so the audio can never be stretched thinner than one pixel per second times the container.
deltaThreshold number 5 Minimum accumulated scroll delta before a zoom step fires. Raising this value debounces fast trackpad scrolls. Set to 0 for fully fluid zooming (higher CPU usage).
exponentialZooming boolean false When true, zoom steps are calculated using a consistent exponential factor so each step feels the same size regardless of the current zoom level. Uses iterations to determine the factor.
iterations number 20 Number of scroll steps to go from the initial zoom level to maxZoom. Only meaningful when exponentialZooming is true.

Example: exponential zoom

ZoomPlugin.create({
  exponentialZooming: true,
  maxZoom: 2000,
  iterations: 30,
})

Scrolling and centering

Zoom level is expressed as minPxPerSec — pixels per second of audio. Related WaveSurfer options that interact with zoom:

Option Default Description
minPxPerSec 0 Starting zoom level. 0 means the waveform fills the container (fillParent behaviour). Set a higher value to open already zoomed in.
fillParent true When true and minPxPerSec would produce a waveform narrower than the container, the waveform is stretched to fill it. Effectively sets a minimum zoom of containerWidth / duration.
autoScroll true Scroll the container during playback to keep the playhead in view.
autoCenter true When autoScroll is true, keep the playhead centred in the container rather than scrolling only when it reaches the edge.
hideScrollbar false Hide the horizontal scrollbar. Useful when you want a clean look; users can still scroll with the wheel or by dragging on touch.

To start zoomed in, set minPxPerSec on WaveSurfer rather than calling ws.zoom() inside a ready handler. Both work, but the option avoids a brief “flash” at the default zoom level during load.

A typical configuration for a zoomable player that stays centred during playback:

const ws = WaveSurfer.create({
  container: '#waveform',
  url: '/audio.mp3',
  minPxPerSec: 50,      // start at 50 px/s
  autoScroll: true,
  autoCenter: true,
  hideScrollbar: false,
  plugins: [
    ZoomPlugin.create({ scale: 0.5, maxZoom: 1000 }),
  ],
})

Common pitfalls

Performance with heavy plugins. Zooming redraws the waveform canvas. If you are also using the Spectrogram plugin, every zoom step regenerates a large FFT canvas and can cause noticeable lag. Keep deltaThreshold above 0 (the default 5 is a good starting point) to batch scroll events and reduce redraws.

Regions and Timeline can misrender after zoom. After a zoom change, Regions and the Timeline re-render asynchronously. In rare cases — particularly when zoom is changed rapidly — labels or region handles can appear at the wrong position until the next render cycle. If you programmatically zoom and then immediately read region positions, wait for the zoom event to settle first.

Pinch-to-zoom on mobile is now built-in (as of the version that added onTouchStart/onTouchMove handlers). The plugin handles two-finger pinch gestures proportionally. However, if your page sets touch-action: pan-y or a meta viewport that prevents scaling, the touchmove event may be cancelled before the plugin can intercept it — ensure your container does not suppress pointer events.

Conflict with the Hover plugin. The Hover plugin listens to mousemove on the same waveform container. When both plugins are active, rapid scrolling can briefly misplace the hover cursor label because the scroll position changes while the label is being repositioned. This is cosmetic only and resolves on the next mouse move.