<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效果图