feat: image preview gestures (#668)
parent
9e8ee0da41
commit
bd72ecd0e5
|
@ -1,10 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { useImageGesture } from '~/composables/gestures'
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const img = ref()
|
||||
|
||||
const current = computed(() => mediaPreviewList.value[mediaPreviewIndex.value])
|
||||
const hasNext = computed(() => mediaPreviewIndex.value < mediaPreviewList.value.length - 1)
|
||||
const hasPrev = computed(() => mediaPreviewIndex.value > 0)
|
||||
|
||||
useImageGesture(img, {
|
||||
hasNext,
|
||||
hasPrev,
|
||||
onNext() {
|
||||
if (hasNext.value)
|
||||
mediaPreviewIndex.value++
|
||||
},
|
||||
onPrev() {
|
||||
if (hasPrev.value)
|
||||
mediaPreviewIndex.value--
|
||||
},
|
||||
})
|
||||
|
||||
const keys = useMagicKeys()
|
||||
|
||||
whenever(keys.arrowLeft, prev)
|
||||
|
@ -45,7 +62,10 @@ function onClick(e: MouseEvent) {
|
|||
<div i-ri:arrow-left-s-line text-white />
|
||||
</button>
|
||||
<img
|
||||
:src="current.url || current.previewUrl" :alt="current.description || ''" max-h-full max-w-full ma
|
||||
ref="img"
|
||||
:src="current.url || current.previewUrl"
|
||||
:alt="current.description || ''"
|
||||
max-h-full max-w-full ma
|
||||
>
|
||||
|
||||
<div absolute top-0 w-full flex justify-between>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import type { PermissiveMotionProperties } from '@vueuse/motion'
|
||||
import type { Handlers } from '@vueuse/gesture'
|
||||
import { useMotionProperties, useSpring } from '@vueuse/motion'
|
||||
import { useGesture } from '@vueuse/gesture'
|
||||
import type { MaybeRef } from '@vueuse/core'
|
||||
|
||||
export interface CarouselOptions {
|
||||
hasNext: MaybeRef<boolean>
|
||||
hasPrev: MaybeRef<boolean>
|
||||
onPrev: () => void
|
||||
onNext: () => void
|
||||
}
|
||||
|
||||
export const useImageGesture = (
|
||||
domTarget: MaybeRef<HTMLElement>,
|
||||
carouselOptions?: CarouselOptions,
|
||||
) => {
|
||||
const { motionProperties } = useMotionProperties(domTarget, {
|
||||
cursor: 'grab',
|
||||
scale: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
|
||||
const { set } = useSpring(motionProperties as Partial<PermissiveMotionProperties>)
|
||||
|
||||
// @ts-expect-error we need to fix types: just suppress it for now
|
||||
const handlers: Handlers = {
|
||||
onPinch({ offset: [d] }) {
|
||||
set({ scale: 1 + d / 200 })
|
||||
},
|
||||
onDragStart() {
|
||||
set({ cursor: 'grabbing' })
|
||||
},
|
||||
onDrag({ movement: [x, y], pinching }) {
|
||||
if (!pinching)
|
||||
set({ x, y, cursor: 'grabbing' })
|
||||
},
|
||||
onDragEnd({ vxvy: [vx], pinching }) {
|
||||
if (pinching)
|
||||
return
|
||||
|
||||
set({ cursor: 'grab' })
|
||||
if (carouselOptions) {
|
||||
const isSwipe = Math.abs(vx) > 0.25
|
||||
if (isSwipe) {
|
||||
if (vx > 0 && unref(carouselOptions.hasPrev))
|
||||
carouselOptions.onPrev()
|
||||
|
||||
else if (vx < 0 && unref(carouselOptions.hasNext))
|
||||
carouselOptions.onNext()
|
||||
}
|
||||
}
|
||||
set({ x: 0, y: 0 })
|
||||
},
|
||||
onMove({ movement: [x, y], dragging, pinching }) {
|
||||
if (dragging && !pinching)
|
||||
set({ x, y })
|
||||
},
|
||||
onWheel({ event, dragging, pinching }) {
|
||||
if (!dragging && !pinching && event.altKey) {
|
||||
event.preventDefault()
|
||||
// @ts-expect-error why is ts complaining here (motionProperties.scale)?
|
||||
set({ scale: motionProperties.scale + event.deltaY * 0.001 })
|
||||
}
|
||||
},
|
||||
onDblclick() {
|
||||
set({ scale: 1 })
|
||||
},
|
||||
onTouchstart(event) {
|
||||
if (event.touches === 2)
|
||||
set({ scale: 1 })
|
||||
},
|
||||
}
|
||||
|
||||
useGesture(handlers, { domTarget })
|
||||
}
|
|
@ -37,7 +37,9 @@
|
|||
"@tiptap/suggestion": "2.0.0-beta.204",
|
||||
"@tiptap/vue-3": "2.0.0-beta.204",
|
||||
"@vueuse/core": "^9.9.0",
|
||||
"@vueuse/gesture": "2.0.0-beta.1",
|
||||
"@vueuse/integrations": "^9.9.0",
|
||||
"@vueuse/motion": "2.0.0-beta.12",
|
||||
"blurhash": "^2.0.4",
|
||||
"browser-fs-access": "^0.31.1",
|
||||
"floating-vue": "2.0.0-beta.20",
|
||||
|
|
106
pnpm-lock.yaml
106
pnpm-lock.yaml
|
@ -33,7 +33,9 @@ specifiers:
|
|||
'@vitejs/plugin-vue': ^3.2.0
|
||||
'@vue-macros/nuxt': ^0.2.2
|
||||
'@vueuse/core': ^9.9.0
|
||||
'@vueuse/gesture': 2.0.0-beta.1
|
||||
'@vueuse/integrations': ^9.9.0
|
||||
'@vueuse/motion': 2.0.0-beta.12
|
||||
'@vueuse/nuxt': ^9.9.0
|
||||
blurhash: ^2.0.4
|
||||
browser-fs-access: ^0.31.1
|
||||
|
@ -94,7 +96,9 @@ dependencies:
|
|||
'@tiptap/suggestion': 2.0.0-beta.204
|
||||
'@tiptap/vue-3': 2.0.0-beta.204
|
||||
'@vueuse/core': 9.9.0
|
||||
'@vueuse/gesture': 2.0.0-beta.1
|
||||
'@vueuse/integrations': 9.9.0_ha7ivgav6uqpoo2b5thfugqwjq
|
||||
'@vueuse/motion': 2.0.0-beta.12
|
||||
blurhash: 2.0.4
|
||||
browser-fs-access: 0.31.1
|
||||
floating-vue: 2.0.0-beta.20
|
||||
|
@ -2708,6 +2712,10 @@ packages:
|
|||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: true
|
||||
|
||||
/@types/web-bluetooth/0.0.14:
|
||||
resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==}
|
||||
dev: false
|
||||
|
||||
/@types/web-bluetooth/0.0.16:
|
||||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
||||
|
||||
|
@ -3498,6 +3506,23 @@ packages:
|
|||
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
|
||||
dev: true
|
||||
|
||||
/@vueuse/core/8.9.4:
|
||||
resolution: {integrity: sha512-B/Mdj9TK1peFyWaPof+Zf/mP9XuGAngaJZBwPaXBvU3aCTZlx3ltlrFFFyMV4iGBwsjSCeUCgZrtkEj9dS2Y3Q==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.1.0
|
||||
vue: ^2.6.0 || ^3.2.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.14
|
||||
'@vueuse/metadata': 8.9.4
|
||||
'@vueuse/shared': 8.9.4
|
||||
vue-demi: 0.13.11
|
||||
dev: false
|
||||
|
||||
/@vueuse/core/9.9.0:
|
||||
resolution: {integrity: sha512-JdDb7TrE0imZnwBhMF4+0PCJqGD3AxzH8S2sfk54P0rqvklK+EAtAR/mPb1HwV/JPujQFQJhghQ190Yq03YpVw==}
|
||||
dependencies:
|
||||
|
@ -3509,6 +3534,21 @@ packages:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
/@vueuse/gesture/2.0.0-beta.1:
|
||||
resolution: {integrity: sha512-HTLibLy3bh6TjRnDAbMAvHSsEmrkRituMj2x+mHwmp1EnM8A8CDRTfNJEr8d/hIairnPPp5Va2KWYVmyP/zvkA==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.4.1
|
||||
vue: ^2.0.0 || >=3.0.0-rc.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
consola: 2.15.3
|
||||
upath: 2.0.1
|
||||
vue-demi: 0.13.11
|
||||
dev: false
|
||||
|
||||
/@vueuse/head/1.0.19_vue@3.2.45:
|
||||
resolution: {integrity: sha512-UB8Vij9fjLS+VIL8VnRxkkkX1dosQEgdfq7kyHNev5tMzAlUc1BwIRlSU5PtJv9+Zk46BhTNdh/Btp+dEinWFQ==}
|
||||
peerDependencies:
|
||||
|
@ -3570,9 +3610,30 @@ packages:
|
|||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/metadata/8.9.4:
|
||||
resolution: {integrity: sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==}
|
||||
dev: false
|
||||
|
||||
/@vueuse/metadata/9.9.0:
|
||||
resolution: {integrity: sha512-pgxsUJv/d7IjKpLeB6TthggEsaBwM3ffc5jPrr5TmxAm/fup0mGR5VTzrdA/PSx85tpb+CIvP92D+55qBNc8ag==}
|
||||
|
||||
/@vueuse/motion/2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-cAZqXexLX6xo+H1N1Mv+wBSSqG4wB+BdjIuHQ50jwlelXCDxSi8gj0K/9nDS+aUZtWh6YMwS6UGCKg58jMVglA==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.4.1
|
||||
vue: ^2.0.0 || >=3.0.0-rc.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@vueuse/core': 8.9.4
|
||||
'@vueuse/shared': 8.9.4
|
||||
framesync: 6.1.2
|
||||
popmotion: 11.0.5
|
||||
style-value-types: 5.1.2
|
||||
vue-demi: 0.13.11
|
||||
dev: false
|
||||
|
||||
/@vueuse/nuxt/9.9.0_nuxt@3.0.0:
|
||||
resolution: {integrity: sha512-bhvHsy3vM38WWRhFgyjbDP/tfn0AMP7z1KeaWt5+ysxGHZ1EHMsY4lcFbpcFlQ6K9TKMcYvzzcBSzYxlWanMnQ==}
|
||||
peerDependencies:
|
||||
|
@ -3591,6 +3652,20 @@ packages:
|
|||
- vue
|
||||
dev: true
|
||||
|
||||
/@vueuse/shared/8.9.4:
|
||||
resolution: {integrity: sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.1.0
|
||||
vue: ^2.6.0 || ^3.2.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
dependencies:
|
||||
vue-demi: 0.13.11
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared/9.9.0:
|
||||
resolution: {integrity: sha512-+D0XFwHG0T+uaIbCSlROBwm1wzs71B7n3KyDOxnvfEMMHDOzl09rYKwaE2AENmYwYPXfHPbSBRDD2gBVHbvTcg==}
|
||||
dependencies:
|
||||
|
@ -5763,6 +5838,12 @@ packages:
|
|||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||
dev: true
|
||||
|
||||
/framesync/6.1.2:
|
||||
resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
|
||||
dependencies:
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/fresh/0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -6093,6 +6174,10 @@ packages:
|
|||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/hey-listen/1.0.8:
|
||||
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
|
||||
dev: false
|
||||
|
||||
/hookable/5.4.2:
|
||||
resolution: {integrity: sha512-6rOvaUiNKy9lET1X0ECnyZ5O5kSV0PJbtA5yZUgdEF7fGJEVwSLSislltyt7nFwVVALYHQJtfGeAR2Y0A0uJkg==}
|
||||
dev: true
|
||||
|
@ -7826,6 +7911,15 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/popmotion/11.0.5:
|
||||
resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
|
||||
dependencies:
|
||||
framesync: 6.1.2
|
||||
hey-listen: 1.0.8
|
||||
style-value-types: 5.1.2
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/postcss-calc/8.2.4_postcss@8.4.19:
|
||||
resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
|
||||
peerDependencies:
|
||||
|
@ -9032,6 +9126,13 @@ packages:
|
|||
dependencies:
|
||||
acorn: 8.8.1
|
||||
|
||||
/style-value-types/5.1.2:
|
||||
resolution: {integrity: sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==}
|
||||
dependencies:
|
||||
hey-listen: 1.0.8
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/stylehacks/5.1.1_postcss@8.4.19:
|
||||
resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==}
|
||||
engines: {node: ^10 || ^12 || >=14.0}
|
||||
|
@ -9692,6 +9793,11 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/upath/2.0.1:
|
||||
resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/update-browserslist-db/1.0.10_browserslist@4.21.4:
|
||||
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
|
||||
hasBin: true
|
||||
|
|
Loading…
Reference in New Issue