使用图片作为地图,canvas连线做行动轨迹

<template>
	<view class="container">
		<!-- 可移动区域 -->
		<movable-area class="movable-area" :style="areaStyle">
			<movable-view class="movable-view" :style="viewStyle" direction="all" :scale="true" :scale-min="minScale"
				:scale-max="maxScale">
				<!-- 图片容器 -->
				<view class="image-container">
					<image :src="mapImage" mode="aspectFill" @load="handleImageLoad" @error="handleImageError"
						class="map-image" :style="imageStyle" />
					<canvas canvas-id="originCanvas" class="origin-canvas" :style="canvasStyle"></canvas>
					<!-- 新增小车图片 -->
					<image v-if="showCar" src="/static/小人.png" class="car-image" :style="carStyle" mode="widthFix" />
				</view>
			</movable-view>
		</movable-area>
		<!-- 新增控制按钮 -->
		<button class="control-btn" @click="toggleAnimation" :disabled="disabled">
			开始移动
		</button>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				disabled: false,
				showCar: false,
				animationActive: false,
				currentProgress: 0,
				animationFrameId: null,
				carPosition: {
					x: 0,
					y: 0
				},
				carAngle: 0,
				trajectoryPoints: [], // 存储转换后的轨迹点

				mapImage: '/static/background.png', // 你的地图图片
				areaSize: {
					width: 0,
					height: 0
				},
				// 可移动区域尺寸
				imageInfo: {
					width: 0,
					height: 0
				},
				// 图片原始尺寸
				displaySize: {
					width: 0,
					height: 0
				},
				// 图片显示尺寸
				minScale: 0.5, // 最小缩放比例
				maxScale: 3, // 最大缩放比例
				centerPoint: {
					x: 0,
					y: 0
				}, // 中心点坐标(显示坐标)
				offset: {
					x: 0,
					y: 0
				} // 当前偏移量
			}
		},
		computed: {
			areaStyle() {
				return {
					width: `${this.areaSize.width}px`,
					height: `${this.areaSize.height}px`
				}
			},
			viewStyle() {
				return {
					width: `${this.displaySize.width}px`,
					height: `${this.displaySize.height}px`
				}
			},
			imageStyle() {
				return {
					width: `${this.displaySize.width}px`,
					height: `${this.displaySize.height}px`
				}
			},
			canvasStyle() {
				return {
					width: `${this.displaySize.width}px`,
					height: `${this.displaySize.height}px`
				}
			},
			carStyle() {
				return {
					position: 'absolute',
					width: '30px',
					height: '30px',
					left: '0',
					top: '0',
					transform: `translate(${this.carPosition.x - 15}px, ${this.carPosition.y - 15}px) rotate(${this.carAngle}deg)`,
					transition: 'transform 0.1s linear',
					zIndex: 10
				}
			}
		},
		onReady() {
			this.initSize();
		},
		methods: {
			// 初始化尺寸
			initSize() {
				const systemInfo = uni.getSystemInfoSync();
				this.areaSize = {
					width: systemInfo.windowWidth,
					height: systemInfo.windowHeight
				};
			},

			// 图片加载完成
			async handleImageLoad(e) {
				try {
					const info = await uni.getImageInfo({
						src: this.mapImage
					});
					this.imageInfo = {
						width: info.width,
						height: info.height
					};

					// 计算图片宽高比
					// const imgRatio = info.height / info.width;
					// const containerRatio = this.areaSize.height / this.areaSize.width;

					// // 计算适应容器的最佳尺寸
					// let displayWidth, displayHeight;

					// 优先适应高度
					// displayHeight = this.areaSize.height;
					// // displayWidth = displayHeight / imgRatio;
					// displayWidth = this.areaSize.width;

					// 如果宽度超出容器,则改为适应宽度
					// if (displayWidth > this.areaSize.width) {
					// 	displayWidth = this.areaSize.width;
					// 	displayHeight = displayWidth * imgRatio;
					// }

					this.displaySize = {
						width: this.areaSize.width,
						height: this.areaSize.height
					};

					this.minScale = 0.5; // 初始已经适应,不能再缩小

					// 计算中心点
					this.centerPoint = {
						x: this.areaSize.width / 2,
						y: this.areaSize.height / 2
					};

					this.$nextTick(() => {
						this.drawTrajectory();
					});
				} catch (error) {
					console.error('图片加载失败:', error);
					uni.showToast({
						title: '地图加载失败',
						icon: 'none'
					});
				}
			},
			// 初始化轨迹点
			initTrajectoryPoints() {
				const rawPoints = [{
						x: 0,
						y: 0
					},
					{
						x: 260000,
						y: 0
					},
					{
						x: 0,
						y: -500000
					},
					{
						x: -260000,
						y: 0
					},
					{
						x: 0,
						y: 500000
					},
					{
						x: 300000,
						y: 500000
					},
					{
						x: -300000,
						y: -1000000
					},
				];

				const scale = this.displaySize.width / (this.imageInfo.width * 1000);
				const originX = this.displaySize.width / 2;
				const originY = this.displaySize.height / 2;
				console.log(scale, originX, originY, this.displaySize)

				return rawPoints.map(point => ({
					x: originX + point.x * scale,
					y: originY - point.y * scale
				}));
			},
			drawTrajectory() {
				this.trajectoryPoints = this.initTrajectoryPoints();
				const ctx = uni.createCanvasContext('originCanvas', this);

				// 绘制轨迹线
				ctx.beginPath();
				this.trajectoryPoints.forEach((point, index) => {
					if (index === 0) {
						ctx.moveTo(point.x, point.y);
					} else {
						ctx.lineTo(point.x, point.y);
					}
				});

				ctx.setStrokeStyle("#025ADD");
				ctx.setLineWidth(2);
				ctx.stroke();

				// 绘制标记点
				this.trajectoryPoints.forEach(point => {
					ctx.beginPath();
					ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI);
					ctx.setFillStyle("#00FF00");
					ctx.fill();
				});

				ctx.draw();
				this.showCar = true;
				this.resetCarPosition();
			},
			// 重置小车位置到起点
			resetCarPosition() {
				if (this.trajectoryPoints.length > 0) {
					this.carPosition = {
						x: this.trajectoryPoints[0].x,
						y: this.trajectoryPoints[0].y
					};
					this.updateCarDirection(0);
				}
			},
			// 更新小车方向
			updateCarDirection(currentIndex) {
				if (currentIndex < this.trajectoryPoints.length - 1) {
					const current = this.trajectoryPoints[currentIndex];
					const next = this.trajectoryPoints[currentIndex + 1];
					this.carAngle = Math.atan2(next.y - current.y, next.x - current.x) * 180 / Math.PI;
				}
			},
			// 开始/停止动画
			toggleAnimation() {
				// if (this.animationActive) {
				//           this.stopAnimation();
				// 	this.animationActive = false;
				// } else {
				// this.resetCarPosition();
				this.disabled = true
				this.startAnimation();
				// }
			},
			// 动画核心逻辑
			startAnimation() {
				if (this.trajectoryPoints.length < 2) return;

				const duration = 10000; // 10秒完成全程
				const startTime = Date.now();
				const totalLength = this.trajectoryPoints.length - 1;
				const frameDuration = 16; // 模拟60fps (1000/60 ≈ 16ms)

				this.animationActive = true;
				this.currentProgress = 0;

				const animate = () => {
					if (!this.animationActive) return;

					const elapsed = Date.now() - startTime;
					this.currentProgress = Math.min(elapsed / duration, 1);

					// 计算当前所在轨迹段
					const exactIndex = this.currentProgress * totalLength;
					const currentIndex = Math.floor(exactIndex);
					const segmentProgress = exactIndex - currentIndex;

					if (currentIndex < totalLength) {
						const start = this.trajectoryPoints[currentIndex];
						const end = this.trajectoryPoints[currentIndex + 1];

						this.carPosition.x = start.x + (end.x - start.x) * segmentProgress;
						this.carPosition.y = start.y + (end.y - start.y) * segmentProgress;

						this.updateCarDirection(currentIndex);
					}

					if (this.currentProgress < 1) {
						this.animationTimer = setTimeout(animate, frameDuration);
					} else {
						this.stopAnimation();
						this.disabled = false
					}
				};

				this.animationTimer = setTimeout(animate, frameDuration);
			},
			stopAnimation() {
				if (this.animationTimer) {
					clearTimeout(this.animationTimer);
				}
				this.animationActive = false;
			},



			// 错误处理
			handleImageError(e) {
				uni.showToast({
					title: '图片加载失败',
					icon: 'none'
				});
			}
		}
	}
</script>

<style>
	.container {
		width: 100vw;
		min-height: 100vh;
		overflow: hidden;
	}

	.movable-area {
		width: 100%;
		height: 100%;
		background-color: #f5f5f5;
		/* 调试用,确认区域范围 */
	}

	.movable-view {
		width: 100%;
		height: auto;
		/* 高度由内容决定 */
	}

	.image-container {
		position: relative;
	}

	.map-image {
		display: block;
		width: 100%;
	}

	.origin-canvas {
		position: absolute;
		top: 0;
		left: 0;
		pointer-events: none;
	}

	.car-image {
		will-change: transform;
		/* 优化动画性能 */
	}

	.control-btn {
		position: fixed;
		width: 100%;
		bottom: 10px;
		/* 	left: 50%;
		transform: translateX(-50%); */
		z-index: 100;

	}
</style>

效果图

canvas效果图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值