0
# Timeline and Navigation
1
2
Timeline display, minimap navigation, zoom controls, and hover interactions for enhanced waveform navigation and user experience with time-based visualization aids.
3
4
## Capabilities
5
6
### Timeline Plugin
7
8
Display time labels, notches, and markers below or above the waveform for precise time navigation.
9
10
```typescript { .api }
11
/**
12
* Timeline plugin for displaying time labels and notches
13
*/
14
class TimelinePlugin extends BasePlugin<TimelinePluginEvents, TimelinePluginOptions> {
15
/**
16
* Create a timeline plugin instance
17
* @param options - Timeline configuration options
18
* @returns New TimelinePlugin instance
19
*/
20
static create(options?: TimelinePluginOptions): TimelinePlugin;
21
}
22
23
interface TimelinePluginOptions {
24
/** Height of timeline in pixels, defaults to 20 */
25
height?: number;
26
27
/** Position relative to waveform, defaults to 'afterend' */
28
insertPosition?: 'beforebegin' | 'afterend';
29
30
/** Time interval between major labels in seconds */
31
timeInterval?: number;
32
33
/** Interval between primary labels (larger text) */
34
primaryLabelInterval?: number;
35
36
/** Interval between secondary labels (smaller text) */
37
secondaryLabelInterval?: number;
38
39
/** CSS styles for the timeline container */
40
style?: Partial<CSSStyleDeclaration>;
41
42
/** Format function for time labels */
43
formatTimeCallback?: (seconds: number) => string;
44
45
/** Whether to display labels, defaults to true */
46
displayLabels?: boolean;
47
}
48
49
interface TimelinePluginEvents extends BasePluginEvents {
50
/** When timeline is rendered */
51
'timeline-ready': [];
52
}
53
```
54
55
**Usage Examples:**
56
57
```typescript
58
import Timeline from "wavesurfer.js/dist/plugins/timeline.esm.js";
59
60
// Basic timeline
61
const timeline = Timeline.create({
62
height: 30,
63
timeInterval: 10, // Major marks every 10 seconds
64
primaryLabelInterval: 30, // Large labels every 30 seconds
65
secondaryLabelInterval: 10, // Small labels every 10 seconds
66
});
67
68
wavesurfer.registerPlugin(timeline);
69
70
// Customized timeline appearance
71
const styledTimeline = Timeline.create({
72
height: 40,
73
insertPosition: 'beforebegin', // Above waveform
74
style: {
75
backgroundColor: '#f0f0f0',
76
borderTop: '1px solid #ccc',
77
color: '#333',
78
fontSize: '12px',
79
fontFamily: 'monospace',
80
},
81
formatTimeCallback: (seconds) => {
82
const mins = Math.floor(seconds / 60);
83
const secs = Math.floor(seconds % 60);
84
return `${mins}:${secs.toString().padStart(2, '0')}`;
85
},
86
});
87
88
// Minimal timeline for mobile
89
const mobileTimeline = Timeline.create({
90
height: 20,
91
primaryLabelInterval: 60, // Less frequent labels
92
secondaryLabelInterval: 30,
93
displayLabels: true,
94
style: {
95
fontSize: '10px',
96
},
97
});
98
99
// Timeline event handling
100
timeline.on('timeline-ready', () => {
101
console.log('Timeline rendered');
102
});
103
```
104
105
### Minimap Plugin
106
107
Provide a small overview waveform that serves as a scrollbar and navigation aid for the main waveform.
108
109
```typescript { .api }
110
/**
111
* Minimap plugin for waveform overview and navigation
112
*/
113
class MinimapPlugin extends BasePlugin<MinimapPluginEvents, MinimapPluginOptions> {
114
/**
115
* Create a minimap plugin instance
116
* @param options - Minimap configuration options
117
* @returns New MinimapPlugin instance
118
*/
119
static create(options?: MinimapPluginOptions): MinimapPlugin;
120
}
121
122
interface MinimapPluginOptions {
123
/** Height of minimap in pixels, defaults to 50 */
124
height?: number;
125
126
/** Position relative to waveform container */
127
insertPosition?: string;
128
129
/** Wave color for minimap, defaults to main waveform color */
130
waveColor?: string;
131
132
/** Progress color for minimap, defaults to main progress color */
133
progressColor?: string;
134
135
/** Background color for minimap */
136
backgroundColor?: string;
137
138
/** Border styling for minimap */
139
borderColor?: string;
140
141
/** Opacity of the minimap, defaults to 1 */
142
opacity?: number;
143
144
/** Whether minimap is interactive, defaults to true */
145
interact?: boolean;
146
}
147
148
interface MinimapPluginEvents extends BasePluginEvents {
149
/** When minimap is ready */
150
'minimap-ready': [];
151
152
/** When minimap is clicked */
153
'minimap-click': [progress: number];
154
}
155
```
156
157
**Usage Examples:**
158
159
```typescript
160
import Minimap from "wavesurfer.js/dist/plugins/minimap.esm.js";
161
162
// Basic minimap
163
const minimap = Minimap.create({
164
height: 60,
165
waveColor: '#ddd',
166
progressColor: '#999',
167
});
168
169
wavesurfer.registerPlugin(minimap);
170
171
// Styled minimap
172
const styledMinimap = Minimap.create({
173
height: 80,
174
waveColor: 'rgba(255, 255, 255, 0.8)',
175
progressColor: 'rgba(255, 255, 255, 1)',
176
backgroundColor: '#1a1a1a',
177
borderColor: '#333',
178
opacity: 0.9,
179
});
180
181
// Interactive minimap with events
182
const interactiveMinimap = Minimap.create({
183
height: 50,
184
interact: true,
185
});
186
187
interactiveMinimap.on('minimap-click', (progress) => {
188
console.log(`Minimap clicked at ${(progress * 100).toFixed(1)}%`);
189
// Main waveform automatically seeks to clicked position
190
});
191
192
interactiveMinimap.on('minimap-ready', () => {
193
console.log('Minimap ready for interaction');
194
});
195
```
196
197
### Zoom Plugin
198
199
Provide zoom controls and functionality for detailed waveform examination.
200
201
```typescript { .api }
202
/**
203
* Zoom plugin for waveform zoom controls
204
*/
205
class ZoomPlugin extends BasePlugin<ZoomPluginEvents, ZoomPluginOptions> {
206
/**
207
* Create a zoom plugin instance
208
* @param options - Zoom configuration options
209
* @returns New ZoomPlugin instance
210
*/
211
static create(options?: ZoomPluginOptions): ZoomPlugin;
212
213
/**
214
* Zoom in by the configured scale factor
215
*/
216
zoomIn(): void;
217
218
/**
219
* Zoom out by the configured scale factor
220
*/
221
zoomOut(): void;
222
223
/**
224
* Get current zoom level
225
* @returns Current pixels per second
226
*/
227
getCurrentZoom(): number;
228
}
229
230
interface ZoomPluginOptions {
231
/** Zoom scale factor, defaults to 2 */
232
scale?: number;
233
234
/** Maximum zoom level in pixels per second, defaults to 1000 */
235
maxZoom?: number;
236
237
/** Minimum zoom level in pixels per second, defaults to 1 */
238
minZoom?: number;
239
240
/** Mouse wheel sensitivity, defaults to 1 */
241
deltaThreshold?: number;
242
243
/** Enable mouse wheel zooming, defaults to true */
244
wheelZoom?: boolean;
245
246
/** Enable keyboard zoom shortcuts, defaults to true */
247
keyboardZoom?: boolean;
248
}
249
250
interface ZoomPluginEvents extends BasePluginEvents {
251
/** When zoom level changes */
252
'zoom-changed': [zoomLevel: number];
253
254
/** When zoom reaches maximum level */
255
'zoom-max': [zoomLevel: number];
256
257
/** When zoom reaches minimum level */
258
'zoom-min': [zoomLevel: number];
259
}
260
```
261
262
**Usage Examples:**
263
264
```typescript
265
import Zoom from "wavesurfer.js/dist/plugins/zoom.esm.js";
266
267
// Basic zoom controls
268
const zoom = Zoom.create({
269
scale: 1.5, // 1.5x zoom factor
270
maxZoom: 500,
271
minZoom: 10,
272
});
273
274
wavesurfer.registerPlugin(zoom);
275
276
// Zoom control buttons
277
document.getElementById('zoom-in').addEventListener('click', () => {
278
zoom.zoomIn();
279
});
280
281
document.getElementById('zoom-out').addEventListener('click', () => {
282
zoom.zoomOut();
283
});
284
285
// Advanced zoom with custom controls
286
const advancedZoom = Zoom.create({
287
scale: 2,
288
maxZoom: 1000,
289
minZoom: 5,
290
wheelZoom: true,
291
keyboardZoom: true,
292
deltaThreshold: 5,
293
});
294
295
// Zoom event handling
296
advancedZoom.on('zoom-changed', (zoomLevel) => {
297
console.log(`Zoom level: ${zoomLevel} px/sec`);
298
document.getElementById('zoom-level').textContent = `${zoomLevel}px/s`;
299
updateZoomSlider(zoomLevel);
300
});
301
302
advancedZoom.on('zoom-max', (zoomLevel) => {
303
console.log('Maximum zoom reached');
304
document.getElementById('zoom-in').disabled = true;
305
});
306
307
advancedZoom.on('zoom-min', (zoomLevel) => {
308
console.log('Minimum zoom reached');
309
document.getElementById('zoom-out').disabled = true;
310
});
311
312
// Custom zoom slider
313
const zoomSlider = document.getElementById('zoom-slider');
314
zoomSlider.addEventListener('input', (event) => {
315
const zoomLevel = parseInt(event.target.value);
316
wavesurfer.zoom(zoomLevel);
317
});
318
319
function updateZoomSlider(zoomLevel) {
320
zoomSlider.value = zoomLevel;
321
}
322
```
323
324
### Hover Plugin
325
326
Display vertical line and timestamp on waveform hover for precise time identification.
327
328
```typescript { .api }
329
/**
330
* Hover plugin for showing time information on waveform hover
331
*/
332
class HoverPlugin extends BasePlugin<HoverPluginEvents, HoverPluginOptions> {
333
/**
334
* Create a hover plugin instance
335
* @param options - Hover configuration options
336
* @returns New HoverPlugin instance
337
*/
338
static create(options?: HoverPluginOptions): HoverPlugin;
339
}
340
341
interface HoverPluginOptions {
342
/** Color of the hover line, defaults to '#333' */
343
lineColor?: string;
344
345
/** Width of the hover line, defaults to '1px' */
346
lineWidth?: string;
347
348
/** Background color of time label, defaults to '#fff' */
349
labelBackground?: string;
350
351
/** Text color of time label, defaults to '#000' */
352
labelColor?: string;
353
354
/** Font size of time label, defaults to '12px' */
355
labelSize?: string;
356
357
/** Custom time formatting function */
358
formatTimeCallback?: (seconds: number) => string;
359
360
/** Whether to show the time label, defaults to true */
361
showLabel?: boolean;
362
363
/** Whether to show the hover line, defaults to true */
364
showLine?: boolean;
365
}
366
367
interface HoverPluginEvents extends BasePluginEvents {
368
/** When hover starts */
369
'hover-start': [time: number];
370
371
/** When hover position changes */
372
'hover-move': [time: number];
373
374
/** When hover ends */
375
'hover-end': [];
376
}
377
```
378
379
**Usage Examples:**
380
381
```typescript
382
import Hover from "wavesurfer.js/dist/plugins/hover.esm.js";
383
384
// Basic hover functionality
385
const hover = Hover.create({
386
lineColor: '#ff0000',
387
lineWidth: '2px',
388
labelBackground: 'rgba(0, 0, 0, 0.8)',
389
labelColor: '#fff',
390
labelSize: '14px',
391
});
392
393
wavesurfer.registerPlugin(hover);
394
395
// Custom time formatting
396
const customHover = Hover.create({
397
formatTimeCallback: (seconds) => {
398
const mins = Math.floor(seconds / 60);
399
const secs = (seconds % 60).toFixed(2);
400
return `${mins}:${secs.padStart(5, '0')}`;
401
},
402
showLabel: true,
403
showLine: true,
404
});
405
406
// Hover event handling for custom actions
407
hover.on('hover-start', (time) => {
408
console.log(`Hover started at ${time}s`);
409
showTimeTooltip(time);
410
});
411
412
hover.on('hover-move', (time) => {
413
updateTimeTooltip(time);
414
415
// Show additional information at hover position
416
showContextualInfo(time);
417
});
418
419
hover.on('hover-end', () => {
420
console.log('Hover ended');
421
hideTimeTooltip();
422
hideContextualInfo();
423
});
424
425
// Minimal hover for mobile (line only)
426
const mobileHover = Hover.create({
427
lineColor: 'rgba(255, 255, 255, 0.8)',
428
lineWidth: '1px',
429
showLabel: false, // Hide label on mobile
430
showLine: true,
431
});
432
```
433
434
### Navigation Integration
435
436
Combine navigation plugins for comprehensive waveform control and user experience.
437
438
**Usage Examples:**
439
440
```typescript
441
// Complete navigation setup
442
function setupCompleteNavigation() {
443
// Timeline for time reference
444
const timeline = Timeline.create({
445
height: 25,
446
timeInterval: 5,
447
primaryLabelInterval: 15,
448
secondaryLabelInterval: 5,
449
formatTimeCallback: formatTimeMmSs,
450
});
451
452
// Minimap for overview
453
const minimap = Minimap.create({
454
height: 50,
455
waveColor: '#e0e0e0',
456
progressColor: '#888',
457
});
458
459
// Zoom controls
460
const zoom = Zoom.create({
461
scale: 1.8,
462
maxZoom: 800,
463
minZoom: 20,
464
});
465
466
// Hover feedback
467
const hover = Hover.create({
468
lineColor: '#4A90E2',
469
formatTimeCallback: formatTimeMmSs,
470
});
471
472
// Register all plugins
473
[timeline, minimap, zoom, hover].forEach(plugin => {
474
wavesurfer.registerPlugin(plugin);
475
});
476
477
// Synchronized zoom updates
478
zoom.on('zoom-changed', (level) => {
479
updateZoomIndicator(level);
480
saveZoomPreference(level);
481
});
482
483
// Minimap click feedback
484
minimap.on('minimap-click', (progress) => {
485
const time = progress * wavesurfer.getDuration();
486
showSeekFeedback(time);
487
});
488
}
489
490
// Navigation keyboard shortcuts
491
function setupKeyboardNavigation() {
492
document.addEventListener('keydown', (event) => {
493
if (!wavesurfer) return;
494
495
switch (event.key) {
496
case 'ArrowLeft':
497
wavesurfer.skip(-5); // Skip back 5 seconds
498
event.preventDefault();
499
break;
500
case 'ArrowRight':
501
wavesurfer.skip(5); // Skip forward 5 seconds
502
event.preventDefault();
503
break;
504
case '=':
505
case '+':
506
zoom?.zoomIn();
507
event.preventDefault();
508
break;
509
case '-':
510
zoom?.zoomOut();
511
event.preventDefault();
512
break;
513
case ' ':
514
wavesurfer.playPause();
515
event.preventDefault();
516
break;
517
case 'Home':
518
wavesurfer.setTime(0);
519
event.preventDefault();
520
break;
521
case 'End':
522
wavesurfer.setTime(wavesurfer.getDuration());
523
event.preventDefault();
524
break;
525
}
526
});
527
}
528
529
// Navigation state persistence
530
function setupNavigationPersistence() {
531
// Save navigation state
532
function saveNavigationState() {
533
const state = {
534
zoom: zoom?.getCurrentZoom() || wavesurfer.options.minPxPerSec,
535
position: wavesurfer.getCurrentTime(),
536
scroll: wavesurfer.getScroll(),
537
};
538
localStorage.setItem('waveform-navigation', JSON.stringify(state));
539
}
540
541
// Restore navigation state
542
function restoreNavigationState() {
543
const saved = localStorage.getItem('waveform-navigation');
544
if (saved) {
545
const state = JSON.parse(saved);
546
547
// Restore zoom
548
if (state.zoom) {
549
wavesurfer.zoom(state.zoom);
550
}
551
552
// Restore position and scroll
553
if (state.position) {
554
wavesurfer.setTime(state.position);
555
}
556
if (state.scroll) {
557
wavesurfer.setScroll(state.scroll);
558
}
559
}
560
}
561
562
// Save state on changes
563
wavesurfer.on('zoom', saveNavigationState);
564
wavesurfer.on('scroll', saveNavigationState);
565
wavesurfer.on('seeking', saveNavigationState);
566
567
// Restore on load
568
wavesurfer.on('ready', restoreNavigationState);
569
}
570
571
// Responsive navigation
572
function setupResponsiveNavigation() {
573
const isMobile = window.innerWidth < 768;
574
575
// Mobile-optimized plugins
576
if (isMobile) {
577
const mobileTimeline = Timeline.create({
578
height: 20,
579
primaryLabelInterval: 30,
580
style: { fontSize: '10px' },
581
});
582
583
const mobileHover = Hover.create({
584
showLabel: false, // Touch interfaces don't need hover labels
585
lineColor: 'rgba(255, 255, 255, 0.8)',
586
});
587
588
// No minimap on mobile to save space
589
wavesurfer.registerPlugin(mobileTimeline);
590
wavesurfer.registerPlugin(mobileHover);
591
} else {
592
// Full desktop navigation
593
setupCompleteNavigation();
594
}
595
596
// Handle orientation changes
597
window.addEventListener('orientationchange', () => {
598
setTimeout(() => {
599
// Refresh navigation layout
600
wavesurfer.getActivePlugins().forEach(plugin => {
601
if (plugin.onResize) plugin.onResize();
602
});
603
}, 100);
604
});
605
}
606
607
// Utility functions
608
function formatTimeMmSs(seconds) {
609
const mins = Math.floor(seconds / 60);
610
const secs = Math.floor(seconds % 60).toString().padStart(2, '0');
611
return `${mins}:${secs}`;
612
}
613
614
function showSeekFeedback(time) {
615
const feedback = document.getElementById('seek-feedback');
616
feedback.textContent = `Seeking to ${formatTimeMmSs(time)}`;
617
feedback.style.display = 'block';
618
setTimeout(() => {
619
feedback.style.display = 'none';
620
}, 1000);
621
}
622
```