拖拽
思路
- 利用 touch 事件来获取元素移动的位置,通过设置元素的样式(top,left)来控制元素的移动
- 拖拽点移动距离 = 移动实时坐标 - 初始坐标
- 元素移动距离 = 元素初始值 + 拖拽点移动距离
实现
<template>
<div :style="`top:${rect.y}px;left:${rect.x}px;`" @touchstart="onTouchstart" @touchmove="onTouchmove"></div>
</template>
const rect = ref({ x: 0, y: 0 })
const onTouchstart = (e: TouchEvent) => {
const { clientX, clientY } = e.changedTouches[0]
startPoint = { x: clientX, y: clientY } // 开始点
startRect = { ...rect.value } // 开始元素位置
}
const onTouchmove = (e: TouchEvent) => {
const { clientX, clientY } = e.changedTouches[0]
const diffX = clientX - startPoint.x // 手指横向移动距离
const diffY = clientY - startPoint.y // 手指纵向移动距离
const distX = startRect.x + diffX // 元素移动的距离
const distY = startRect.y + diffY // 元素移动的距离
rect.value.x = distX
rect.value.y = distY
}
旋转
思路
- 元素旋转是根据元素中心点旋转,先计算元素的中心点坐标,再计算元素旋转的角度
- 元素旋转的角度:以元素中心点为坐标系原点,开始点为坐标系上的点,触摸点为目标点,通过目标点的反正切得出对应的弧度,再通过弧度计算出角度
- ? 注意:此角度只有每个象限的角度,所以你需要根据当前角度所在象限进行角度换算
- 如果你觉得四个象限麻烦,可以考虑使用
Math.atan2 计算角度,这里使用的是 Math.atan - 中心点:
- x = rect.w / 2 + rect.x
- y = rect.h / 2 + rect.y
- 弧度 = Math.atan(x / y)
- 角度 = 弧度 * 180 / Math.PI
实现
interface Point {
x: number
y: number
}
const rect = ref({ x: 0, y: 0, w: 100, h: 100 })
const getCenterPoint = (): Point => {
const { w, h, x: ex, y: ey } = rect
const x = w / 2 + ex
const y = h / 2 + ey
return { x, y }
}
const getRotate = (point: Point): number => {
const center = getCenterPoint()
if (point.x === center.x) {
return point.y >= center.y ? 0 : 180
}
if (point.y === center.y) {
return point.x < center.x ? 90 : 270
}
const x = point.x - center.x
const y = point.y - center.y
let angle = (Math.atan(Math.abs(x / y)) / Math.PI) * 180
// 默认从第三象限(x<0 && y>0)开始为正
if (x < 0 && y < 0) {
// 第二象限
angle = 180 - angle
} else if (x > 0 && y < 0) {
// 第一象限
angle += 180
} else if (x > 0 && y > 0) {
// 第四象限
angle = 360 - angle
}
return angle
}
const angle = getRotate({ x: 100, y: 100 }) // 角度
缩放
左上(西北)方向
- 思路:通过开始点和中心点计算出对称点,然后通过移动目标点和对称点计算出新的中心点,根据移动目标点和新的中心点和角度可以计算出角度为 0 时的元素坐标点,元素坐标点和新的中心点计算出新的对称点,再根据新的对称点和元素坐标点计算元素的大小,也可以直接根据元素坐标点和新的中心点计算出元素大小,这里为了和其它方位统一所以使用新的对称点计算元素大小
- 计算出的新的宽高保持为正值,后续不用重新获取绝对值
- 对称点 = 中心点 x 2 - 开始点
- 新的中心点 = ( 目标点 + 对称点 ) / 2
- 元素坐标点 = (目标点 - 新的中心点) x Math.cos(角度 x π / 180) - (目标点 - 新的中心点) x Math.sin(角度 x π / 180) + 新的中心点
- 新的对称点 = 新的中心点 x 2 - 目标点
- 元素大小 = 新的对称点 - 元素坐标点
- 实现:
/**
* 左上(西北)方向
* @param { Option } param - 计算参数
* @returns { Rect } 计算之后的大小
*/
const northWestResize = ({ symmPoint, curPoint, rect }: Option): Rect => {
const newCenter = getCenterPoint(curPoint, symmPoint) // 新的中心点坐标
const newPoint = getRotatePoint(curPoint, newCenter, -rect.r) // 新的坐标点
const newSymmPoint = getSymmPoint(newPoint, newCenter) // 新的对称点
const newW = newSymmPoint.x - newPoint.x // 新的宽度
const newH = newSymmPoint.y - newPoint.y // 新的高度
if (newW > 0 && newH > 0) {
rect.w = Math.round(newW)
rect.h = Math.round(newH)
rect.x = Math.round(newPoint.x)
rect.y = Math.round(newPoint.y)
}
return rect
}
上(北)方向
- 思路:通过移动目标点和开始点和角度计算角度为 0° 时的新坐标,然后通过新坐标和开始点和角度计算出新的旋转坐标,根据勾股定理计算元素高度,根据旋转坐标和对称点计算出新的中心点,通过新的中心点和元素大小计算出元素的坐标
- 新坐标 = (目标点 - 开始点) x Math.cos(角度 x π / 180) - (目标点 - 开始点) x Math.sin(角度 x π / 180) + 开始点
- 旋转坐标 = (新坐标 - 开始点) x Math.cos(角度 x π / 180) - (新坐标 - 开始点) x Math.sin(角度 x π / 180) + 开始点
- 因为是往上拖拽,所以新坐标的横坐标和开始点的坐标相同 (新坐标.x = 开始点.x)
- 元素高度 = Math.sqrt((旋转坐标 - 对称坐标)2 + (旋转坐标 - 对称坐标)2)
- 新的中心点 = (旋转坐标 + 对称坐标) / 2
- 实现:
/**
* 上(北)方向
* @param { Option } param - 计算参数
* @returns { Rect } 计算之后的大小
*/
const northResize = ({ startPoint, symmPoint, curPoint, rect }: Option): Rect => {
const rotatePoint = getRotatePoint(curPoint, startPoint, -rect.r)
const centerTop = getRotatePoint(
{
x: startPoint.x,
y: rotatePoint.y
},
startPoint,
rect.r
)
const newH = Math.sqrt((centerTop.x - symmPoint.x) ** 2 + (centerTop.y - symmPoint.y) ** 2)
if (newH > 0) {
const newCenter = {
x: (centerTop.x + symmPoint.x) / 2,
y: (centerTop.y + symmPoint.y) / 2
}
rect.h = Math.round(newH)
rect.x = Math.round(newCenter.x - rect.w / 2)
rect.y = Math.round(newCenter.y - newH / 2)
}
return rect
}
实例
|