uniapp上手机端二维图片轨迹路线回放
template view classbox !-- 地图容器背景底图 轨迹画布重叠 -- view classtrajectory-card !-- 底层场地背景图 -- image classmap-bg src/static/ceshi.png modeaspectFill/image !-- 顶层画布绘制轨迹、人物绑定点击事件拿物理坐标 -- canvas idtrackCanvas canvas-idtrackCanvas classtrack-canvas taphandleCanvasTap/canvas /view !-- 回放控制按钮组 -- view styledisplay:flex;gap:20rpx;margin:30rpx van-button clickhandleClick重新播放/van-button van-button typeprimary clickpausePlay{{ isPause ? 继续 : 暂停 }}/van-button /view !-- 回放进度条 -- view stylemargin:0 20rpx view当前绘制进度{{ progressNum }}%/view view stylewidth:100%;height:20rpx;background:#eee;border-radius:10rpx;margin-top:10rpx; view styleheight:100%;background:#025ADD;border-radius:10rpx; :style{width:progressNum%}/view /view /view /view /template script import { uwbPosition } from ../../api/index.js // 场地四个角落对应的真实UWB物理坐标X东/Y北Y上大下小 const COORD { leftTopX: 0.35, leftTopY: 167.57, rightTopX: 83.27, rightTopY: 167.57, leftBottomX: 0.35, leftBottomY: 128.39, rightBottomX: 83.27, rightBottomY: 128.39 } export default { data() { return { delay: 30, // 轨迹点绘制间隔(ms)500ms画一个点位 timer: null, // 定时器标识用于逐帧渲染轨迹 canvasContainerSize: { width: 0, height: 0 }, // 画布真实宽高 trajectoryPoints: [], // 后端返回全部UWB轨迹点位数组 canvasCtx: null, // canvas绘图上下文所有绘制操作依赖它 currentIndex: 0, // 当前渲染到第几个轨迹点 progressNum: 0, // 回放进度百分比 0~100 isPause: false, // 新增暂停标记 false播放 true暂停 } }, // 页面初次渲染完成加载接口初始化画布 onReady() { // 请求后端UWB轨迹接口获取人员历史坐标 uwbPosition({ cardId: 614959, startTime: 2026-07-24 09:08:49, endTime: 2026-07-24 23:53:58 }).then(res { // 接口赋值轨迹数组无数据则为空数组 this.trajectoryPoints res.data.data || [] }) // 获取画布绘制上下文uni canvas固定写法 this.canvasCtx uni.createCanvasContext(trackCanvas, this) }, // 页面销毁清除定时器防止内存泄漏、后台持续渲染 onUnload() { if (this.timer) clearTimeout(this.timer) }, methods: { /** * 真实物理坐标(UWB) → 画布像素坐标 * param {Number} realX 真实X东 * param {Number} realY 真实Y北 * param {Number} canvasW 画布宽度 * param {Number} canvasH 画布高度 * returns {Object} {x,y} 画布像素点位 */ realToCanvas(realX, realY, canvasW, canvasH) { // 提取场地物理边界极值 const worldMaxX COORD.rightTopX; const worldMinX COORD.leftTopX; const worldMaxY COORD.leftTopY; const worldMinY COORD.leftBottomY; // 场地总物理宽、总物理高 const worldWidth worldMaxX - worldMinX; const worldHeight worldMaxY - worldMinY; // 计算X轴比例当前X占场地总宽度百分比 let xRatio (realX - worldMinX) / worldWidth; // Y轴翻转物理Y顶部大、画布Y顶部0反向换算比例 let yRatio (worldMaxY - realY) / worldHeight; // 限制比例0~1防止坐标超出画布看不见 xRatio Math.max(0, Math.min(1, xRatio)); yRatio Math.max(0, Math.min(1, yRatio)); // 比例 × 画布尺寸 最终画布像素 const canvasX xRatio * canvasW; const canvasY yRatio * canvasH; return { x: canvasX, y: canvasY } }, /** * 画布点击像素坐标 → 真实UWB物理坐标反向换算打点标定用 * param {Number} canvasX 画布点击X像素 * param {Number} canvasY 画布点击Y像素 * param {Number} canvasW 画布宽 * param {Number} canvasH 画布高 * returns {Object} {card_x,card_y} 真实物理坐标 */ canvasToReal(canvasX, canvasY, canvasW, canvasH) { const worldMaxX COORD.rightTopX; const worldMinX COORD.leftTopX; const worldMaxY COORD.leftTopY; const worldMinY COORD.leftBottomY; const worldWidth worldMaxX - worldMinX; const worldHeight worldMaxY - worldMinY; // 点击位置占画布比例 const xRatio canvasX / canvasW; const yRatio canvasY / canvasH; // 比例换算回真实物理XY const realX worldMinX xRatio * worldWidth; const realY worldMaxY - yRatio * worldHeight; return { card_x: realX, card_y: realY } }, // 画布点击触发弹出当前点位真实物理坐标 async handleCanvasTap(e) { // 先获取画布宽高 await this.getCanvasContainerRealSize() const { width, height } this.canvasContainerSize // e.detail.x/y 点击相对画布的像素坐标 const canvasX e.detail.x const canvasY e.detail.y // 反向换算物理坐标 const realPos this.canvasToReal(canvasX, canvasY, width, height) console.log(点击像素, canvasX, canvasY, 物理坐标, realPos) uni.showModal({ title: 点位物理坐标, content: X:${realPos.card_x.toFixed(2)} Y:${realPos.card_y.toFixed(2)} }) }, // 获取canvas画布渲染后的真实宽高rpx转像素实际尺寸 getCanvasContainerRealSize() { return new Promise(resolve { uni.createSelectorQuery().in(this) .select(#trackCanvas) .boundingClientRect(rect { // 存在尺寸则赋值保存 if (rect?.width) this.canvasContainerSize rect resolve() }).exec() }) }, // 暂停/继续切换 pausePlay() { // 切换暂停状态 this.isPause !this.isPause // 如果从暂停切为播放继续执行下一帧绘制 if (!this.isPause) { this.drawStep(this.canvasContainerSize.width, this.canvasContainerSize.height, this.allPointDataCache) } }, // 回放按钮点击入口初始化参数、转换全部坐标、启动逐帧绘制 async handleClick() { // 清除上一次回放定时器防止多重回放叠加 if (this.timer) clearTimeout(this.timer) this.timer null this.currentIndex 0 this.progressNum 0 this.isPause false // 重新播放默认解除暂停 // 获取画布尺寸 await this.getCanvasContainerRealSize() const { width, height } this.canvasContainerSize // 画布尺寸获取失败弹窗提示 if (!width || !height) return uni.showToast({ title: 画布获取失败, icon: none }) // 轨迹点位不足2个无法画线直接终止 if (this.trajectoryPoints.length 2) return uni.showToast({ title: 轨迹数据不足, icon: none }) // 一次性把所有后端物理坐标转为画布像素避免逐帧重复计算性能优化 const allPointData this.trajectoryPoints.map(item { const pos this.realToCanvas(Number(item.card_x), Number(item.card_y), width, height) return { ...item, px: pos } }) // 缓存全部点位暂停恢复时使用 this.allPointDataCache allPointData // 启动逐帧绘制 this.drawStep(width, height, allPointData) }, /** * 逐帧递归绘制轨迹核心渲染函数 * param {Number} canvasW 画布宽 * param {Number} canvasH 画布高 * param {Array} allPointData 全部转换完成的轨迹点位 */ drawStep(canvasW, canvasH, allPointData) { // 如果是暂停状态直接终止不再继续下一帧 if (this.isPause) return const allPx allPointData.map(d d.px) const total allPx.length // 计算当前回放进度百分比 this.progressNum Math.round((this.currentIndex / (total - 1)) * 100) // 截取从起点到当前索引的所有点位逐步延长轨迹线 const drawDataList allPointData.slice(0, this.currentIndex 1) const drawPoints drawDataList.map(d d.px) // 当前最新点位绘制人物头像 const lastData drawDataList[drawDataList.length - 1] const lastPos lastData.px const ctx this.canvasCtx // 清空整幅画布避免上次绘制残留 ctx.clearRect(0, 0, canvasW, canvasH) // 轨迹点2个才绘制连线 if (drawPoints.length 2) { ctx.beginPath() // 第一个点位移动画笔起点其余点位连线 drawPoints.forEach((p, i) i 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y)) ctx.setStrokeStyle(#130C6D) // 轨迹线颜色 ctx.setLineWidth(2) // 线条宽度 ctx.lineCap round // 线条端点圆角 ctx.lineJoin round // 线条拐角圆角 ctx.stroke() // 执行绘制线条 } // 保存当前绘图样式防止文字/图片样式污染 ctx.save() // 人物头像图片路径 const avatarImg /static/xiaoren.png const imgW 24 // 头像宽 const imgH 24 // 头像高 // 头像坐标居中在轨迹点上 const imgX lastPos.x - imgW / 2 const imgY lastPos.y - imgH / 2 // 绘制头像图片 ctx.drawImage(avatarImg, imgX, imgY, imgW, imgH) // 人员名称文字配置 const userName lastData.name || 丁丁 ctx.font bold 14px sans-serif ctx.textAlign center // 文字居中对齐 const textX lastPos.x const textY imgY - 10 // 文字放在头像上方 // 黑色文字描边增强可读性 ctx.strokeStyle #000000 ctx.lineWidth 2 ctx.strokeText(userName, textX, textY) // 白色填充文字 ctx.fillStyle #ffffff ctx.fillText(userName, textX, textY) // 恢复绘图样式 ctx.restore() // 将所有绘制指令渲染到画布上 ctx.draw() // 未绘制完所有点位定时器递归调用自身继续绘制下一个点 if (this.currentIndex allPx.length - 1) { this.currentIndex this.timer setTimeout(() { this.drawStep(canvasW, canvasH, allPointData) }, this.delay) } else { // 全部点位绘制完成弹出成功提示 uni.showToast({ title: 轨迹绘制完成, icon: success }) } } } } /script style scoped .box { background: gray; height: 100vh; } .trajectory-card { margin: 20rpx 0; width: 100%; height: 600rpx; border-radius: 15rpx; position: relative; overflow: hidden; background: #fff; } /* 背景图、画布铺满容器完全重叠 */ .map-bg, .track-canvas { width: 100%; height: 100%; position: absolute; left: 0; top: 0; } /style/static/xiaoren.png是人物图片uwbPosition是接口里返回的数据

相关新闻

TwinCAT 半导体设备测试程序架构设计:高性能、高灵活度的完整技术方案

TwinCAT 半导体设备测试程序架构设计:高性能、高灵活度的完整技术方案

摘要 本文详细阐述基于 Beckhoff TwinCAT 平台开发半导体设备自动化测试程序的完整技术方案。方案覆盖对工艺制程、配方、故障、报警、IO变量等核心要素的测试,并实现自动报告生成与日志记录。文章从技术架构、软件分层、通信驱动、UI界面四个维度进行深度设计分析…

2026/7/29 22:17:25 阅读更多 →
Rocky Linux on WSL 安装 Neo4j 完整方案

Rocky Linux on WSL 安装 Neo4j 完整方案

📋 方案概述本方案基于官方 RPM 仓库,在 Rocky Linux(运行于 WSL)系统上安装 Neo4j 图数据库。由于 WSL 的特殊网络环境,需要特别注意 IP 地址的获取和 Windows 宿主机的访问配置。🚀 详细安装步骤第一步&a…

2026/7/29 22:17:25 阅读更多 →
电商AI批量生图效率优化实践:从10张/日到100张/日的系统工程

电商AI批量生图效率优化实践:从10张/日到100张/日的系统工程

商AI单图生成耗时相对固定,单纯提升单图速度存在瓶颈。本文从系统工程角度,通过流程拆解、瓶颈分析、模板化改造、批量并行策略、质量分级管控,完成电商AI生图全链路效率优化。实测将单SKU平均作图时长由30min压缩至6.5min,日产出…

2026/7/29 22:17:25 阅读更多 →

最新新闻

终极REPENTOGON脚本扩展器:为《以撒的结合》注入全新活力的完整指南

终极REPENTOGON脚本扩展器:为《以撒的结合》注入全新活力的完整指南

终极REPENTOGON脚本扩展器:为《以撒的结合》注入全新活力的完整指南 【免费下载链接】REPENTOGON Script extender for The Binding of Isaac: Repentance 项目地址: https://gitcode.com/gh_mirrors/re/REPENTOGON 想要彻底改变你的《以撒的结合&#xff1a…

2026/7/29 22:27:29 阅读更多 →
年终总结PPT哪个AI工具自动排版效果最好?实测5款主流工具,第一名出人意料

年终总结PPT哪个AI工具自动排版效果最好?实测5款主流工具,第一名出人意料

又到年底了,你是不是也在对着空白PPT页面发呆? 做年终总结PPT,最折磨人的不是“没内容可写”——这一年的工作成果其实心里有数。真正让人崩溃的是:把内容整理成逻辑清晰的大纲,再一页页排版、配图、调格式。一套操作下…

2026/7/29 22:27:29 阅读更多 →
macOS用户必看:gh_mirrors/do/dotfiles.zsh系统优化与defaults设置指南

macOS用户必看:gh_mirrors/do/dotfiles.zsh系统优化与defaults设置指南

macOS用户必看:gh_mirrors/do/dotfiles.zsh系统优化与defaults设置指南 【免费下载链接】dotfiles.zsh Config files for ZSH, Java, Ruby, Go, Editors, Terminals and more. 项目地址: https://gitcode.com/gh_mirrors/do/dotfiles.zsh gh_mirrors/do/dotf…

2026/7/29 22:27:29 阅读更多 →
EventBus与微服务架构:打造松耦合、高可用的分布式系统

EventBus与微服务架构:打造松耦合、高可用的分布式系统

EventBus与微服务架构:打造松耦合、高可用的分布式系统 【免费下载链接】event_bus :surfer: Traceable, extendable and minimalist **event bus** implementation for Elixir with built-in **event store** and **event watcher** based on ETS. 项目地址: htt…

2026/7/29 22:27:29 阅读更多 →
如何快速上手node-modbus-serial?5分钟搭建你的第一个Modbus TCP客户端

如何快速上手node-modbus-serial?5分钟搭建你的第一个Modbus TCP客户端

如何快速上手node-modbus-serial?5分钟搭建你的第一个Modbus TCP客户端 【免费下载链接】node-modbus-serial A pure JavaScript implemetation of MODBUS-RTU (and TCP) for NodeJS 项目地址: https://gitcode.com/gh_mirrors/no/node-modbus-serial node-m…

2026/7/29 22:27:28 阅读更多 →
Java 反序列化漏洞基础:从 readObject 到命令执行

Java 反序列化漏洞基础:从 readObject 到命令执行

Java 反序列化漏洞基础:从 readObject 到命令执行 写在前面 前面我们复现了 Log4j2 和 fastjson 的漏洞,它们都属于"JNDI 注入"那一挂–触发点是组件自己的特性(Log4j2 的 lookup、fastjson 的 autoType),最…

2026/7/29 22:26:28 阅读更多 →

日新闻

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:00:23 阅读更多 →
AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础 在上一期「AI编程系列」中,我们学习了如何构建一个基础的 AI 问答系统,通过简单的输入输出让模型回应问题。但现实世界中的 AI 应用往往需要处理更复杂的场景:…

2026/7/29 0:00:23 阅读更多 →
AI智能体开发实战:从工具调用到企业级部署

AI智能体开发实战:从工具调用到企业级部署

1. 从被动问答到主动执行:AI Agent的范式转变过去两年,大语言模型最显著的应用形态是聊天机器人——用户提问,AI回答。但真正的生产力革命发生在2023年下半年:当AI学会主动调用工具完成任务时,生产力工具的历史被彻底改…

2026/7/29 0:00:23 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/29 22:18:20 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/29 15:00:03 阅读更多 →

月新闻