Skip to content

Commit

Permalink
Feat(Zoom): Add exponential zooming and a few bugfixes (#3934)
Browse files Browse the repository at this point in the history
* Added exponential zooming and a few bugfixes

* Adressed review comments

---------

Co-authored-by: gustavso <[email protected]>
  • Loading branch information
gustavsollenius and gustavso authored Nov 21, 2024
1 parent 5f480f3 commit 50e779b
Showing 1 changed file with 56 additions and 7 deletions.
63 changes: 56 additions & 7 deletions src/plugins/zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @author HoodyHuo (https://github.com/HoodyHuo)
* @author Chris Morbitzer (https://github.com/cmorbitzer)
* @author Sam Hulick (https://github.com/ffxsam)
* @autor Gustav Sollenius (https://github.com/gustavsollenius)
*
* @example
* // ... initialising wavesurfer with the plugin
Expand Down Expand Up @@ -36,10 +37,26 @@ export type ZoomPluginOptions = {
* @default 5
*/
deltaThreshold?: number
/**
* Whether to zoom into the waveform using a consistent exponential factor instead of a linear scale.
* Exponential zooming ensures the zoom steps feel uniform regardless of scale.
* When disabled, the zooming is linear and influenced by the `scale` parameter.
*
* @default false
*/
exponentialZooming?: boolean
/**
* Number of steps required to zoom from the initial zoom level to `maxZoom`.
*
* @default 20
*/
iterations?: number
}
const defaultOptions = {
scale: 0.5,
deltaThreshold: 5,
exponentialZooming: false,
iterations: 20,
}

export type ZoomPluginEvents = BasePluginEvents
Expand All @@ -49,6 +66,10 @@ class ZoomPlugin extends BasePlugin<ZoomPluginEvents, ZoomPluginOptions> {
private wrapper: HTMLElement | undefined = undefined
private container: HTMLElement | null = null
private accumulatedDelta = 0
private pointerTime: number = 0
private oldX: number = 0
private endZoom: number = 0
private startZoom: number = 0

constructor(options?: ZoomPluginOptions) {
super(options || {})
Expand All @@ -65,7 +86,12 @@ class ZoomPlugin extends BasePlugin<ZoomPluginEvents, ZoomPluginOptions> {
return
}
this.container = this.wrapper.parentElement as HTMLElement
this.wrapper.addEventListener('wheel', this.onWheel)
this.container.addEventListener('wheel', this.onWheel)

if (typeof this.options.maxZoom === 'undefined') {
this.options.maxZoom = this.container.clientWidth
}
this.endZoom = this.options.maxZoom
}

private onWheel = (e: WheelEvent) => {
Expand All @@ -78,14 +104,27 @@ class ZoomPlugin extends BasePlugin<ZoomPluginEvents, ZoomPluginOptions> {
// Update the accumulated delta...
this.accumulatedDelta += -e.deltaY

if (this.startZoom === 0 && this.options.exponentialZooming) {
this.startZoom = this.wavesurfer.getWrapper().clientWidth / this.wavesurfer.getDuration()
}

// ...and only scroll once we've hit our threshold
if (this.options.deltaThreshold === 0 || Math.abs(this.accumulatedDelta) >= this.options.deltaThreshold) {
const duration = this.wavesurfer.getDuration()
const oldMinPxPerSec = this.wavesurfer.options.minPxPerSec
const x = e.clientX
const oldMinPxPerSec =
this.wavesurfer.options.minPxPerSec === 0
? this.wavesurfer.getWrapper().scrollWidth / duration
: this.wavesurfer.options.minPxPerSec
const x = e.clientX - this.container.getBoundingClientRect().left
const width = this.container.clientWidth
const scrollX = this.wavesurfer.getScroll()
const pointerTime = (scrollX + x) / oldMinPxPerSec

// Update pointerTime only if the pointer position has changed. This prevents the waveform from drifting during fixed zooming.
if (x !== this.oldX || this.oldX === 0) {
this.pointerTime = (scrollX + x) / oldMinPxPerSec
}
this.oldX = x

const newMinPxPerSec = this.calculateNewZoom(oldMinPxPerSec, this.accumulatedDelta)
const newLeftSec = (width / newMinPxPerSec) * (x / width)

Expand All @@ -94,7 +133,7 @@ class ZoomPlugin extends BasePlugin<ZoomPluginEvents, ZoomPluginOptions> {
this.container.scrollLeft = 0
} else {
this.wavesurfer.zoom(newMinPxPerSec)
this.container.scrollLeft = (pointerTime - newLeftSec) * newMinPxPerSec
this.container.scrollLeft = (this.pointerTime - newLeftSec) * newMinPxPerSec
}

// Reset the accumulated delta
Expand All @@ -103,8 +142,18 @@ class ZoomPlugin extends BasePlugin<ZoomPluginEvents, ZoomPluginOptions> {
}

private calculateNewZoom = (oldZoom: number, delta: number) => {
const newZoom = Math.max(0, oldZoom + delta * this.options.scale)
return typeof this.options.maxZoom === 'undefined' ? newZoom : Math.min(newZoom, this.options.maxZoom)
let newZoom
if (this.options.exponentialZooming) {
const zoomFactor =
delta > 0
? Math.pow(this.endZoom / this.startZoom, 1 / (this.options.iterations - 1))
: Math.pow(this.startZoom / this.endZoom, 1 / (this.options.iterations - 1))
newZoom = Math.max(0, oldZoom * zoomFactor)
} else {
// Default linear zooming
newZoom = Math.max(0, oldZoom + delta * this.options.scale)
}
return Math.min(newZoom, this.options.maxZoom!)
}

destroy() {
Expand Down

0 comments on commit 50e779b

Please sign in to comment.