Make media modal be aware of multi-touch actions

Originally the media viewer would think every touch is a swipe (one-finger
touch event), so we would encounter the case where a two-finger scale event
would incorrectly change the current media. This is now fixed.
This commit is contained in:
Tusooa Zhu 2021-08-01 13:39:56 -04:00
parent 425919a0d2
commit 61509d1b1e
No known key found for this signature in database
GPG key ID: 7B467EDE43A08224
3 changed files with 93 additions and 16 deletions

View file

@ -41,25 +41,22 @@ const MediaModal = {
} }
}, },
created () { created () {
this.mediaSwipeGestureRight = GestureService.swipeGesture( this.mediaGesture = new GestureService.SwipeAndScaleGesture({
GestureService.DIRECTION_RIGHT, direction: GestureService.DIRECTION_LEFT,
this.goPrev, callbackPositive: this.goNext,
50 callbackNegative: this.goPrev,
) threshold: 50
this.mediaSwipeGestureLeft = GestureService.swipeGesture( })
GestureService.DIRECTION_LEFT,
this.goNext,
50
)
}, },
methods: { methods: {
mediaTouchStart (e) { mediaTouchStart (e) {
GestureService.beginSwipe(e, this.mediaSwipeGestureRight) this.mediaGesture.start(e)
GestureService.beginSwipe(e, this.mediaSwipeGestureLeft)
}, },
mediaTouchMove (e) { mediaTouchMove (e) {
GestureService.updateSwipe(e, this.mediaSwipeGestureRight) this.mediaGesture.move(e)
GestureService.updateSwipe(e, this.mediaSwipeGestureLeft) },
mediaTouchEnd (e) {
this.mediaGesture.end(e)
}, },
hide () { hide () {
this.$store.dispatch('closeMediaViewer') this.$store.dispatch('closeMediaViewer')

View file

@ -12,6 +12,7 @@
:title="currentMedia.description" :title="currentMedia.description"
@touchstart.stop="mediaTouchStart" @touchstart.stop="mediaTouchStart"
@touchmove.stop="mediaTouchMove" @touchmove.stop="mediaTouchMove"
@touchend.stop="mediaTouchEnd"
@click="hide" @click="hide"
> >
<VideoAttachment <VideoAttachment

View file

@ -4,9 +4,17 @@ const DIRECTION_RIGHT = [1, 0]
const DIRECTION_UP = [0, -1] const DIRECTION_UP = [0, -1]
const DIRECTION_DOWN = [0, 1] const DIRECTION_DOWN = [0, 1]
const isSwipeEvent = e => (e.touches.length === 1)
const isSwipeEventEnd = e => (e.changedTouches.length === 1)
const isScaleEvent = e => (e.targetTouches.length === 2)
// const isScaleEventEnd = e => (e.changedTouches.length === 2)
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY]) const touchCoord = touch => [touch.screenX, touch.screenY]
const touchEventCoord = e => touchCoord(e.touches[0])
const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1]) const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1])
@ -61,6 +69,76 @@ const updateSwipe = (event, gesture) => {
gesture._swiping = false gesture._swiping = false
} }
class SwipeAndScaleGesture {
constructor ({
direction, callbackPositive, callbackNegative,
previewCallback, threshold = 30, perpendicularTolerance = 1.0
}) {
this.direction = direction
this.previewCallback = previewCallback
this.callbackPositive = callbackPositive
this.callbackNegative = callbackNegative
this.threshold = threshold
this.perpendicularTolerance = perpendicularTolerance
this._startPos = [0, 0]
this._swiping = false
}
start (event) {
console.log('start() called', event)
if (isSwipeEvent(event)) {
this._startPos = touchEventCoord(event)
console.log('start pos:', this._startPos)
this._swiping = true
} else if (isScaleEvent(event)) {
this._scalePoints = [...event.targetTouches]
this._swiping = false
}
}
move (event) {
if (isScaleEvent(event)) {
}
}
end (event) {
console.log('end() called', event)
if (!isSwipeEventEnd(event)) {
console.log('not swipe event')
return
}
if (!this._swiping) {
console.log('not swiping')
return
}
this.swiping = false
console.log('is swipe event')
// movement too small
const touch = event.changedTouches[0]
const delta = deltaCoord(this._startPos, touchCoord(touch))
if (vectorLength(delta) < this.threshold) return
// movement is opposite from direction
const isPositive = dotProduct(delta, this.direction) > 0
// movement perpendicular to direction is too much
const towardsDir = project(delta, this.direction)
const perpendicularDir = perpendicular(this.direction)
const towardsPerpendicular = project(delta, perpendicularDir)
if (
vectorLength(towardsDir) * this.perpendicularTolerance <
vectorLength(towardsPerpendicular)
) return
if (isPositive) {
this.callbackPositive()
} else {
this.callbackNegative()
}
}
}
const GestureService = { const GestureService = {
DIRECTION_LEFT, DIRECTION_LEFT,
DIRECTION_RIGHT, DIRECTION_RIGHT,
@ -68,7 +146,8 @@ const GestureService = {
DIRECTION_DOWN, DIRECTION_DOWN,
swipeGesture, swipeGesture,
beginSwipe, beginSwipe,
updateSwipe updateSwipe,
SwipeAndScaleGesture
} }
export default GestureService export default GestureService