<template>
<div
class="w-full h-full outline-none overflow-hidden relative flex justify-center items-center cursor-grab active:cursor-grabbing"
tabindex="-1"
@pointerdown="onPressDown"
>
<div ref="containerRef" class="absolute will-change-transform cursor-default">
<slot />
</div>
</div>
</template>
<script setup>
/**
* 鼠标按下时的位置
*/
const position = {
x: 0,
y: 0
}
/**
* @constant
* @type {import('vue').Ref<HTMLElement>}
* @description 用于获取容器的 ref
*/
const containerRef = ref()
function getPositions (ev) {
if (ev instanceof MouseEvent) {
return {
x: ev.clientX,
y: ev.clientY
}
}
return {
x: ev.touches[0].clientX,
y: ev.touches[0].clientY
}
}
/**
* 当按下鼠标或手指
* @param {MouseEvent|TouchEvent} ev 事件对象
*/
function onPressDown (ev) {
// 如果不是点击的鼠标左键,则不处理
if (ev instanceof MouseEvent && ev.button !== 0) {
return
}
// 防止在拖动的过程中选中文字
document.body.classList.add('select-none')
// 记录按下时的位置
Object.assign(position, getPositions(ev))
document.addEventListener('pointerup', onPressUp, { once: true })
document.addEventListener('pointermove', onPressMove, { passive: true })
}
/**
* 当按下并移动鼠标或手指
* @param {MouseEvent|TouchEvent} ev 事件对象
*/
function onPressMove (ev) {
const { x, y } = getPositions(ev)
const elStyle = containerRef.value.style
// 计算鼠标/手指按下到这一次移动的偏移量
const offsetX = x - position.x
const offsetY = y - position.y
// 获取当前的 transform 位置
const [, currentX = 0, currentY = 0] = elStyle.transform.match(/translate3d\((.+?)px, (.+?)px, 0px\)/) ?? []
elStyle.transform = `translate3d(${~~currentX + offsetX}px, ${~~currentY + offsetY}px, 0)`
// 更新上一次移动的位置
Object.assign(position, { x, y })
}
/**
* 当松开鼠标或手指
*/
function onPressUp () {
document.body.classList.remove('select-none')
document.removeEventListener('pointermove', onPressMove)
}
</script>