Skip to content

Commit

Permalink
Fix: drag on mobile (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
katspaugh authored Nov 26, 2023
1 parent 747c33e commit 715add5
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 61 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wavesurfer-multitrack",
"version": "0.4.3",
"version": "0.4.4",
"license": "BSD-3-Clause",
"author": "katspaugh",
"description": "Multritrack super-plugin for wavesurfer.js",
Expand Down
83 changes: 83 additions & 0 deletions src/draggable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
export function makeDraggable(
element: HTMLElement | null,
onDrag: (dx: number, dy: number, x: number, y: number) => void,
onStart?: (x: number, y: number) => void,
onEnd?: () => void,
button = 0,
threshold = 5,
): () => void {
if (!element) return () => void 0

let unsubscribeDocument = () => void 0

const onPointerDown = (event: PointerEvent) => {
if (event.button !== button) return

let startX = event.clientX
let startY = event.clientY
let isDragging = false

const onPointerMove = (event: PointerEvent) => {
const x = event.clientX
const y = event.clientY
const dx = x - startX
const dy = y - startY
startX = x
startY = y

if (isDragging || Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
const rect = element.getBoundingClientRect()
const { left, top } = rect

if (!isDragging) {
onStart?.(startX - left, startY - top)
isDragging = true
}

onDrag(dx, dy, x - left, y - top)
}
}

const onPointerUp = () => {
if (isDragging) {
isDragging = false
onEnd?.()
}
unsubscribeDocument()
}

const onClick = (event: MouseEvent) => {
event.stopPropagation()
event.preventDefault()
}

const onTouchMove = (event: TouchEvent) => {
if (isDragging) {
event.preventDefault()
}
}

document.addEventListener('pointermove', onPointerMove)
document.addEventListener('pointerup', onPointerUp)
document.addEventListener('pointercancel', onPointerUp)
document.addEventListener('touchmove', onTouchMove, { passive: false })
element.addEventListener('click', onClick, { capture: true })

unsubscribeDocument = () => {
document.removeEventListener('pointermove', onPointerMove)
document.removeEventListener('pointerup', onPointerUp)
document.removeEventListener('pointercancel', onPointerUp)
document.removeEventListener('touchmove', onTouchMove)
setTimeout(() => {
element.removeEventListener('click', onClick, { capture: true })
}, 0)
}
}

element.addEventListener('pointerdown', onPointerDown)

return () => {
unsubscribeDocument()
element.removeEventListener('pointerdown', onPointerDown)
}
}
97 changes: 37 additions & 60 deletions src/multitrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.js'
import TimelinePlugin, { type TimelinePluginOptions } from 'wavesurfer.js/dist/plugins/timeline.js'
import EnvelopePlugin, { type EnvelopePoint, type EnvelopePluginOptions } from 'wavesurfer.js/dist/plugins/envelope.js'
import EventEmitter from 'wavesurfer.js/dist/event-emitter.js'
import { makeDraggable } from './draggable.js'
import WebAudioPlayer from './webaudio.js'

export type TrackId = string | number
Expand Down Expand Up @@ -123,8 +124,12 @@ class MultiTrack extends EventEmitter<MultitrackEvents> {

this.rendering.containers.forEach((container, index) => {
if (tracks[index]?.draggable) {
const drag = initDragging(container, (delta: number) => this.onDrag(index, delta), options.rightButtonDrag)
this.wavesurfers[index].once('destroy', () => drag?.destroy())
const unsubscribe = initDragging(
container,
(delta: number) => this.onDrag(index, delta),
options.rightButtonDrag,
)
this.wavesurfers[index].once('destroy', unsubscribe)
}
})

Expand Down Expand Up @@ -533,8 +538,12 @@ class MultiTrack extends EventEmitter<MultitrackEvents> {
this.wavesurfers[index].destroy()
this.wavesurfers[index] = this.initWavesurfer(track, index)

const drag = initDragging(container, (delta: number) => this.onDrag(index, delta), this.options.rightButtonDrag)
this.wavesurfers[index].once('destroy', () => drag?.destroy())
const unsubscribe = initDragging(
container,
(delta: number) => this.onDrag(index, delta),
this.options.rightButtonDrag,
)
this.wavesurfers[index].once('destroy', unsubscribe)

this.initTimeline()

Expand Down Expand Up @@ -715,70 +724,38 @@ function initRendering(tracks: MultitrackTracks, options: MultitrackOptions) {
}

function initDragging(container: HTMLElement, onDrag: (delta: number) => void, rightButtonDrag = false) {
const wrapper = container.parentElement
if (!wrapper) return

// Dragging tracks to set position
let dragStart: number | null = null
let isDragging = false
let overallWidth = 0

container.addEventListener('contextmenu', (e) => {
rightButtonDrag && e.preventDefault()
})
const unsubscribe = makeDraggable(
container,
(dx: number) => {
onDrag(dx / overallWidth)
},
() => {
container.style.cursor = 'grabbing'
overallWidth = container.parentElement?.offsetWidth ?? 0
},
() => {
container.style.cursor = 'grab'
},
rightButtonDrag ? 2 : 0,
)

// Drag start
container.addEventListener('pointerdown', (e) => {
if (rightButtonDrag && e.button !== 2) return
const rect = wrapper.getBoundingClientRect()
dragStart = e.clientX - rect.left
container.style.cursor = 'grabbing'
})
const preventDefault = (e: Event) => e.preventDefault()

// Drag end
const onMouseUp = (e: MouseEvent) => {
if (dragStart != null) {
e.stopPropagation()
dragStart = null
container.style.cursor = ''
container.style.cursor = 'grab'

setTimeout(() => (isDragging = false), 100)
}
if (rightButtonDrag) {
container.addEventListener('contextmenu', preventDefault)
}

// Drag move
const onMouseMove = (e: MouseEvent) => {
if (dragStart == null) return
const rect = wrapper.getBoundingClientRect()
const x = e.clientX - rect.left
const diff = x - dragStart
if (diff > 1 || diff < -1) {
isDragging = true
dragStart = x
onDrag(diff / wrapper.offsetWidth)
return () => {
container.style.cursor = ''
unsubscribe()
if (rightButtonDrag) {
container.removeEventListener('contextmenu', preventDefault)
}
}

const onClick = (e: MouseEvent) => {
if (isDragging) {
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
}
}

document.addEventListener('pointerup', onMouseUp)
document.addEventListener('pointerleave', onMouseUp)
document.addEventListener('pointermove', onMouseMove)
wrapper.addEventListener('click', onClick)

return {
destroy: () => {
document.removeEventListener('pointerup', onMouseUp)
document.removeEventListener('pointerleave', onMouseUp)
document.removeEventListener('pointermove', onMouseMove)
wrapper.removeEventListener('click', onClick)
},
}
}

export default MultiTrack

0 comments on commit 715add5

Please sign in to comment.