Hover plugin
The Hover plugin draws a vertical line and an elapsed-time label that follow the mouse cursor over the waveform. It works with zero configuration and is the quickest way to give users a scrub-preview experience.
See it in action: live Hover example.
Setup
Install
The plugin ships with wavesurfer.js — no separate package is needed.
import WaveSurfer from 'wavesurfer.js'
import HoverPlugin from 'wavesurfer.js/dist/plugins/hover.esm.js'
Or via CDN (ESM):
import HoverPlugin from 'https://unpkg.com/wavesurfer.js@7/dist/plugins/hover.esm.js'
Register the plugin
Pass a plugin instance to WaveSurfer.create() through the plugins array:
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
plugins: [
HoverPlugin.create(),
],
})
No further setup is required. The cursor line and label appear as soon as the mouse enters the waveform.
Options
All options are optional. When lineColor is not set the plugin inherits the waveform’s cursorColor, then progressColor, as a fallback.
| Option | Type | Default | Description |
|---|---|---|---|
lineColor |
string |
(inherited) | Color of the vertical cursor line. Falls back to the main WaveSurfer cursorColor, then progressColor, when omitted. |
lineWidth |
string | number |
1 |
Width of the cursor line. Plain numbers are treated as pixels; pass a string such as '2px' to be explicit. |
labelBackground |
string |
(none) | Background color of the timestamp label. Leave unset for a transparent label. |
labelColor |
string |
(inherited) | Color of the timestamp label text. |
labelSize |
string | number |
11 |
Font size of the label text. Plain numbers are treated as pixels. |
labelPreferLeft |
boolean |
false |
When true the label appears to the left of the cursor whenever there is enough space, instead of to the right. |
formatTimeCallback |
(seconds: number) => string |
m:ss |
Custom function to format the timestamp string shown in the label. |
formatTimeCallback
The callback receives one argument — the hovered position in seconds — and must return a string. The default formats as m:ss:
HoverPlugin.create({
formatTimeCallback: (seconds) => {
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
return `${m}:${String(s).padStart(2, '0')}`
},
})
To show milliseconds, compute the fractional part of seconds:
formatTimeCallback: (seconds) => {
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
const ms = Math.round((seconds % 1) * 1000)
return `${m}:${String(s).padStart(2, '0')}.${String(ms).padStart(3, '0')}`
},
Events
The plugin emits one event:
| Event | Arguments | Description |
|---|---|---|
hover |
relX: number |
Fired on every pointermove over the waveform. relX is the cursor’s horizontal position as a fraction of the total waveform width, from 0 (start) to 1 (end). |
const hover = HoverPlugin.create()
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
plugins: [hover],
})
hover.on('hover', (relX) => {
// relX is between 0 and 1
const duration = ws.getDuration()
console.log('Hovered at', (relX * duration).toFixed(2), 's')
})
Common pitfalls
Hover + Zoom together can cause a jittery or blank cursor line. When the Zoom plugin is active it changes the waveform’s scroll position and rendered dimensions while the user is also moving the mouse — a rapid series of resize and scroll events can make the hover line flicker or disappear entirely. Both plugins are designed to co-exist (the Hover plugin re-computes its position on zoom and scroll events), but a few things help keep the combination smooth:
- Register both plugins at construction time in the
pluginsarray. Registering them at different times can cause the hover line to be sized against a stale wrapper width. - If you set a very low
debounceTimeon the Zoom plugin (or0), every scroll tick triggers a full re-render. SettingdebounceTimeto at least200ms significantly reduces the number of concurrent updates and eliminates most jitter. - On low-powered devices, setting a shorter
barWidthor disablingnormalizeon the main WaveSurfer instance reduces render cost and makes both plugins feel more responsive.
See Zoom plugin for debounceTime and related options.