uniapp canvas 实现单指拖动、双指拖动和双指缩放

单指拖动

图片从蓝色框的位置拖到橙色框的位置,在开始触摸的时候记录开始触摸时手指到页面左侧的距离 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>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值