forked from zhongyidian/super_video
292 lines
8.6 KiB
TypeScript
292 lines
8.6 KiB
TypeScript
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { getTouchDistance, getTouchCenter, getClientPosition } from '../utils/touchGestures'
|
|
|
|
// 设计稿尺寸常量
|
|
const DESIGN_WIDTH = 1920
|
|
const DESIGN_HEIGHT = 1080
|
|
|
|
/**
|
|
* 自由取景框模式的逻辑
|
|
* 支持拖动、四角调整大小、双指缩放
|
|
*/
|
|
export function useViewfinderB() {
|
|
// 取景框相关
|
|
const viewfinderContainer = ref<HTMLElement>()
|
|
const viewfinderBox = ref<HTMLElement>()
|
|
const viewfinder = ref({
|
|
x: 660, // (1920 - 600) / 2 = 660,居中
|
|
y: 340, // (1080 - 400) / 2 = 340,居中
|
|
width: 600,
|
|
height: 400
|
|
})
|
|
|
|
// 拖动相关状态
|
|
const isDragging = ref(false)
|
|
const dragStart = ref({ x: 0, y: 0 })
|
|
|
|
// 调整大小相关状态
|
|
const isResizing = ref(false)
|
|
const resizeCorner = ref<'nw' | 'ne' | 'sw' | 'se' | null>(null)
|
|
const resizeStart = ref({
|
|
x: 0,
|
|
y: 0,
|
|
boxX: 0,
|
|
boxY: 0,
|
|
boxWidth: 0,
|
|
boxHeight: 0
|
|
})
|
|
|
|
// 双指缩放相关状态
|
|
const isPinching = ref(false)
|
|
const pinchStart = ref({
|
|
distance: 0,
|
|
centerX: 0,
|
|
centerY: 0,
|
|
boxWidth: 0,
|
|
boxHeight: 0,
|
|
boxX: 0,
|
|
boxY: 0
|
|
})
|
|
|
|
// 将屏幕坐标转换为缩放后的本地坐标
|
|
const convertClientToLocal = (clientX: number, clientY: number) => {
|
|
if (!viewfinderContainer.value) return { x: clientX, y: clientY }
|
|
|
|
const containerRect = viewfinderContainer.value.getBoundingClientRect()
|
|
|
|
// 计算缩放比例
|
|
const scaleX = containerRect.width / DESIGN_WIDTH
|
|
const scaleY = containerRect.height / DESIGN_HEIGHT
|
|
|
|
// 计算相对于容器的坐标,并转换为设计稿坐标系
|
|
const localX = (clientX - containerRect.left) / scaleX
|
|
const localY = (clientY - containerRect.top) / scaleY
|
|
|
|
return { x: localX, y: localY }
|
|
}
|
|
|
|
// 开始拖动取景框
|
|
const startDrag = (e: MouseEvent | TouchEvent) => {
|
|
if (isResizing.value || isPinching.value) return
|
|
|
|
// 检测双指触摸
|
|
if ('touches' in e && e.touches.length === 2) {
|
|
// 双指缩放
|
|
isPinching.value = true
|
|
const touch1 = e.touches[0]
|
|
const touch2 = e.touches[1]
|
|
|
|
pinchStart.value = {
|
|
distance: getTouchDistance(touch1, touch2),
|
|
centerX: (touch1.clientX + touch2.clientX) / 2,
|
|
centerY: (touch1.clientY + touch2.clientY) / 2,
|
|
boxWidth: viewfinder.value.width,
|
|
boxHeight: viewfinder.value.height,
|
|
boxX: viewfinder.value.x,
|
|
boxY: viewfinder.value.y
|
|
}
|
|
e.preventDefault()
|
|
return
|
|
}
|
|
|
|
// 单指拖动
|
|
if ('touches' in e && e.touches.length !== 1) return
|
|
|
|
isDragging.value = true
|
|
const { x: clientX, y: clientY } = getClientPosition(e)
|
|
|
|
const localPos = convertClientToLocal(clientX, clientY)
|
|
|
|
dragStart.value = {
|
|
x: localPos.x - viewfinder.value.x,
|
|
y: localPos.y - viewfinder.value.y
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
// 开始调整大小
|
|
const startResize = (corner: 'nw' | 'ne' | 'sw' | 'se', e: MouseEvent | TouchEvent) => {
|
|
isResizing.value = true
|
|
resizeCorner.value = corner
|
|
|
|
const { x: clientX, y: clientY } = getClientPosition(e)
|
|
|
|
const localPos = convertClientToLocal(clientX, clientY)
|
|
|
|
resizeStart.value = {
|
|
x: localPos.x,
|
|
y: localPos.y,
|
|
boxX: viewfinder.value.x,
|
|
boxY: viewfinder.value.y,
|
|
boxWidth: viewfinder.value.width,
|
|
boxHeight: viewfinder.value.height
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
// 处理移动
|
|
const handleMove = (e: MouseEvent | TouchEvent) => {
|
|
if (!isDragging.value && !isResizing.value && !isPinching.value) return
|
|
|
|
// 处理双指缩放
|
|
if (isPinching.value && 'touches' in e && e.touches.length === 2) {
|
|
const touch1 = e.touches[0]
|
|
const touch2 = e.touches[1]
|
|
|
|
const currentDistance = getTouchDistance(touch1, touch2)
|
|
const scale = currentDistance / pinchStart.value.distance
|
|
|
|
// 计算新的尺寸
|
|
let newWidth = pinchStart.value.boxWidth * scale
|
|
let newHeight = pinchStart.value.boxHeight * scale
|
|
|
|
const minSize = 200
|
|
|
|
// 限制最小和最大尺寸
|
|
newWidth = Math.max(minSize, Math.min(newWidth, DESIGN_WIDTH))
|
|
newHeight = Math.max(minSize, Math.min(newHeight, DESIGN_HEIGHT))
|
|
|
|
// 保持中心点不变,计算新的位置
|
|
const center = getTouchCenter(touch1, touch2)
|
|
const localCenter = convertClientToLocal(center.x, center.y)
|
|
|
|
let newX = localCenter.x - newWidth / 2
|
|
let newY = localCenter.y - newHeight / 2
|
|
|
|
// 边界检测
|
|
newX = Math.max(0, Math.min(newX, DESIGN_WIDTH - newWidth))
|
|
newY = Math.max(0, Math.min(newY, DESIGN_HEIGHT - newHeight))
|
|
|
|
viewfinder.value.width = newWidth
|
|
viewfinder.value.height = newHeight
|
|
viewfinder.value.x = newX
|
|
viewfinder.value.y = newY
|
|
|
|
e.preventDefault()
|
|
return
|
|
}
|
|
|
|
const { x: clientX, y: clientY } = getClientPosition(e)
|
|
|
|
const localPos = convertClientToLocal(clientX, clientY)
|
|
|
|
if (isDragging.value && viewfinderContainer.value) {
|
|
// 拖动取景框
|
|
let newX = localPos.x - dragStart.value.x
|
|
let newY = localPos.y - dragStart.value.y
|
|
|
|
// 边界检测 - 使用设计稿尺寸
|
|
newX = Math.max(0, Math.min(newX, DESIGN_WIDTH - viewfinder.value.width))
|
|
newY = Math.max(0, Math.min(newY, DESIGN_HEIGHT - viewfinder.value.height))
|
|
|
|
viewfinder.value.x = newX
|
|
viewfinder.value.y = newY
|
|
} else if (isResizing.value && viewfinderContainer.value) {
|
|
// 调整取景框大小
|
|
const deltaX = localPos.x - resizeStart.value.x
|
|
const deltaY = localPos.y - resizeStart.value.y
|
|
|
|
const minSize = 200 // 最小尺寸
|
|
|
|
if (resizeCorner.value === 'se') {
|
|
// 右下角 - 使用设计稿尺寸限制
|
|
viewfinder.value.width = Math.max(
|
|
minSize,
|
|
Math.min(resizeStart.value.boxWidth + deltaX, DESIGN_WIDTH - viewfinder.value.x)
|
|
)
|
|
viewfinder.value.height = Math.max(
|
|
minSize,
|
|
Math.min(resizeStart.value.boxHeight + deltaY, DESIGN_HEIGHT - viewfinder.value.y)
|
|
)
|
|
} else if (resizeCorner.value === 'sw') {
|
|
// 左下角 - 使用设计稿尺寸限制
|
|
const newWidth = resizeStart.value.boxWidth - deltaX
|
|
const newX = resizeStart.value.boxX + deltaX
|
|
if (newWidth >= minSize && newX >= 0) {
|
|
viewfinder.value.x = newX
|
|
viewfinder.value.width = newWidth
|
|
}
|
|
viewfinder.value.height = Math.max(
|
|
minSize,
|
|
Math.min(resizeStart.value.boxHeight + deltaY, DESIGN_HEIGHT - viewfinder.value.y)
|
|
)
|
|
} else if (resizeCorner.value === 'ne') {
|
|
// 右上角 - 使用设计稿尺寸限制
|
|
const newHeight = resizeStart.value.boxHeight - deltaY
|
|
const newY = resizeStart.value.boxY + deltaY
|
|
if (newHeight >= minSize && newY >= 0) {
|
|
viewfinder.value.y = newY
|
|
viewfinder.value.height = newHeight
|
|
}
|
|
viewfinder.value.width = Math.max(
|
|
minSize,
|
|
Math.min(resizeStart.value.boxWidth + deltaX, DESIGN_WIDTH - viewfinder.value.x)
|
|
)
|
|
} else if (resizeCorner.value === 'nw') {
|
|
// 左上角 - 使用设计稿尺寸限制
|
|
const newWidth = resizeStart.value.boxWidth - deltaX
|
|
const newHeight = resizeStart.value.boxHeight - deltaY
|
|
const newX = resizeStart.value.boxX + deltaX
|
|
const newY = resizeStart.value.boxY + deltaY
|
|
|
|
if (newWidth >= minSize && newX >= 0) {
|
|
viewfinder.value.x = newX
|
|
viewfinder.value.width = newWidth
|
|
}
|
|
if (newHeight >= minSize && newY >= 0) {
|
|
viewfinder.value.y = newY
|
|
viewfinder.value.height = newHeight
|
|
}
|
|
}
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
// 结束拖动或调整
|
|
const handleEnd = () => {
|
|
isDragging.value = false
|
|
isResizing.value = false
|
|
isPinching.value = false
|
|
resizeCorner.value = null
|
|
}
|
|
|
|
// 挂载时添加全局事件监听
|
|
onMounted(() => {
|
|
document.addEventListener('mousemove', handleMove)
|
|
document.addEventListener('mouseup', handleEnd)
|
|
document.addEventListener('touchmove', handleMove)
|
|
document.addEventListener('touchend', handleEnd)
|
|
})
|
|
|
|
// 卸载时移除事件监听
|
|
onUnmounted(() => {
|
|
document.removeEventListener('mousemove', handleMove)
|
|
document.removeEventListener('mouseup', handleEnd)
|
|
document.removeEventListener('touchmove', handleMove)
|
|
document.removeEventListener('touchend', handleEnd)
|
|
})
|
|
|
|
return {
|
|
// Refs
|
|
viewfinderContainer,
|
|
viewfinderBox,
|
|
|
|
// 状态
|
|
viewfinder,
|
|
isDragging,
|
|
isResizing,
|
|
isPinching,
|
|
|
|
// 方法
|
|
startDrag,
|
|
startResize,
|
|
|
|
// 常量
|
|
DESIGN_WIDTH,
|
|
DESIGN_HEIGHT
|
|
}
|
|
}
|