单指拖动
图片从蓝色框的位置拖到橙色框的位置,在开始触摸的时候记录开始触摸时手指到页面左侧的距离 startX ,拖动过程中记录手指到页面左侧的距离 currentX ,最后用 currentX 减去 startX 就得到移动的距离 dragX 。
双指拖动
双指拖动原理跟单指拖动一样,多加一步,用双指的中心点去计算,上图的点看成是双指的中心点,其他的一样。
双指缩放
计算双指缩放后图片移动的位移
思路
双指实现图片放大,首先获取图片的初始位置 initX 和 双指中心点到整个页面最左侧的距离 centerX ,得到双指中心点到自身边框的距离 bx ,接着通过缩放比例计算放大后双指中心点到自身边框的距离 ax,通过缩放前的 bx 减去缩放后的 ax ,就得到了缩放后图片在页面上移动的距离 scaleDx 了,y轴同理。
代码
let centerX = (e.changedTouches[0].clientX + e.changedTouches[1].clientX) / 2
let centerY = (e.changedTouches[0].clientY + e.changedTouches[1].clientY) / 2
// 缩放前 双指中心点到自身边框的距离
let bx = centerX - initX
let by = centerY - initY
// 缩放后 双指中心点到自身边框的距离
let ax = bx * (scale.value / lastScale.value)
let ay = by * (scale.value / lastScale.value)
// 缩放后的位移
let scaleDx = bx - ax
let scaleDy = by - ay
这部分实际应用中不需要计算到,直接在 onTouchEnd 方法中去更新最后拖动的位置即可。
完整代码
<template>
<view id="pictureViewer">
<section>
<canvas id="canvasId" :canvas-id="canvasId" @touchstart="onTouchStart" @touchmove="onTouchMove"
@touchend="onTouchEnd"></canvas>
</section>
</view>
</template>
<script setup>
import {
onMounted,
ref,
getCurrentInstance
} from 'vue';
const canvasId = ref('canvasId')
const scale = ref(1); // 缩放比例
const lastScale = ref(1); // 上一次的缩放比例
const touchCount = ref(0) // 触摸的手指数量 1-单指 2-双指
// 单指
const startPoint = ref({
x: 0,
y: 0
}); // 初始触摸点
const currentPoint = ref({
x: 0,
y: 0
}); // 当前触摸点
// 双指
const doubleStartCenterPoint = ref({x: 0, y: 0}) // 初始触摸 两指的中心点
const doubleCurrentCenterPoint = ref({x: 0, y: 0}) // 当前触摸 两指的中心点
const isDragging = ref(false); // 是否正在拖动
const isScaling = ref(false) // 是否正在缩放
let initialDistance = 0; // 用于保存初始两指之间的距离
let initX = 0, initY = 0; // canvas 初始位置
let lastDx = 0, lastDy = 0 // 上一次拖动完成的位置
let dragX = 0, dragY = 0 // 当前拖动完成时,移动了多少距离
let canvasWidth = 0, canvasHeight = 0 // canvas 宽高
const systemInfo = uni.getSystemInfoSync(); // 获取屏幕尺寸信息
const screenWidth = systemInfo.windowWidth; // 屏幕宽度
const screenHeight = systemInfo.windowHeight; // 屏幕高度
// 初始化 canvas
const initCanvas = (dx = 0, dy = 0) => {
const ctx = uni.createCanvasContext(canvasId.value)
uni.createSelectorQuery().select(`#${canvasId.value}`).boundingClientRect(rect => {
canvasWidth = rect.width
canvasHeight = rect.height
// 绘制内容,应用缩放和平移
ctx.save();
// 更新平移位置
ctx.translate(dx, dy);
// 渲染图形的宽高
let width = (canvasWidth - screenWidth * 0.04) * scale.value
let height = (canvasHeight - 500) * scale.value
// 计算初始位置在屏幕中心位置
initX = (screenWidth - width) / 2
initY = (screenHeight - height) / 2
// 清空画布
ctx.clearRect(initX, initY, width, height);
// 绘制白色背景的矩形
ctx.fillStyle = 'white';
ctx.fillRect(initX, initY, width, height);
// 绘制图片 第一个参数是 url
// ctx.drawImage(imageUrl.value, initX, initY, width, height)
ctx.restore(); // 恢复到之前保存的状态
ctx.draw();
}).exec()
}
const onTouchStart = (e) => {
// 阻止默认行为,防止页面滚动
e.preventDefault()
touchCount.value = e.touches.length
// 单指拖动
if (touchCount.value === 1) {
startPoint.value = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
}
isDragging.value = true;
} else if (touchCount.value === 2) {
// 双指拖动
let centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2
let centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2
doubleStartCenterPoint.value = {x: centerX, y: centerY}
// 双指缩放
initialDistance = getDistance(e.touches[0], e.touches[1])
lastScale.value = scale.value; // 保存当前缩放比例
isScaling.value = true;
}
}
const onTouchMove = (e) => {
// 阻止默认行为,防止页面滚动
e.preventDefault()
if (isDragging.value && touchCount.value === 1) {
// 单指拖动
currentPoint.value = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
}
// 计算拖动的距离,由于是默认从初始位置进行渲染的,所以需要加上上一次完成拖动的距离
dragX = currentPoint.value.x - startPoint.value.x + lastDx
dragY = currentPoint.value.y - startPoint.value.y + lastDy
initCanvas(dragX, dragY)
} else if (isScaling.value && touchCount.value === 2) {
// 双指拖动
let centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2
let centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2
doubleCurrentCenterPoint.value = {x: centerX, y: centerY}
// 计算拖动的距离,由于是默认从初始位置进行渲染的,所以需要加上上一次完成拖动的距离
dragX = doubleCurrentCenterPoint.value.x - doubleStartCenterPoint.value.x + lastDx
dragY = doubleCurrentCenterPoint.value.y - doubleStartCenterPoint.value.y + lastDy
// 双指缩放
const distance = getDistance(e.touches[0], e.touches[1]); // 获取当前两指间的距离
scale.value = lastScale.value * (distance / initialDistance); // 计算新的缩放比例
initCanvas(dragX, dragY)
}
}
const onTouchEnd = (e) => {
isDragging.value = false;
if(isScaling.value) {
lastScale.value = scale.value
}
isScaling.value = false
// 更新拖动位置 解决每次拖动/缩放的时候图片默认跳到初始位置的问题
lastDx = dragX
lastDy = dragY
if (e.touches.length < 2) touchCount.value = e.touches.length
}
const getDistance = (touch1, touch2) => {
// X轴的平方差 + Y轴的平方差
return Math.sqrt(
Math.pow(touch1.clientX - touch2.clientX, 2) +
Math.pow(touch1.clientY - touch2.clientY, 2)
);
}
onMounted(() => {
initCanvas()
})
</script>
<style lang="scss">
#pictureViewer {
width: 100vw;
height: 100vh;
background-color: #000;
display: flex;
flex-direction: column;
flex: 1;
flex-basis: auto;
&>section {
flex-grow: 1;
overflow: hidden;
display: flex;
&>canvas {
width: 100%;
height: auto;
}
}
}
</style>