#过年不停更# JS自定义组件之扫地机地图绘制 原创 精华
春节不停更,此文正在参加「星光计划-春节更帖活动」
作者:刘远深
前言
随着5G网络的普及,智能家居也逐步这进入家家户户,其中智能扫地机器人可谓是智能家居所涉及到的技术面是非常的广泛的,除了简单的清扫和拖地功能外,定时预约清扫和扫描家中布局生成地图,对地图进行编辑即可定制不同区域的清扫策略,极大地满足不同人们对家中清扫的各种需求。
在整个智能扫地机器人的周边中,移动端的控制面板是用户使用扫地机器人的方法,而地图又是控制面板组成模块中最重要的组成部分之一。我们简单的了解一下移动端使用js去实现基础的地图。
环境准备
环境
- window 10
- node.js 14.18.1
- vue 2.6.11
开发工具
webstorm 2021.3
效果演示
基础介绍
在绘图方面,我们用到的是 canvas + js。首先扫地机器人在工作的过程中会对工作的区域进行扫描并生成一张区域地图。而扫地机器人扫描的地图数据则会通过接口实时上报,通常上报的数据是一系列坐标点或是一张图片。
此次我们以扫地机器人上报给我们的地图数据为 base64 格式的图片进行开发演示。本章我们先实现将地图显示出来。
编码演示
- 先创建一个vue组件。
<template>
<section class="canvasBox" id="canvasBox">
</section>
</template>
<script>
export default {
data: () => ({
}),
props: {
},
};
</script>
<style scoped>
</style>
-
添加 canvas 标签和相关的属性,在如下的属性中,map为地图的核心数据,通常该数据是有通过 ajax 从云端获取。在 map 的属性中,data 为 base64 格式的地图数据,其数据形式可参考gitee,height 和 width 为地图的宽高。
<template> <section class="canvasBox" id="canvasBox"> <canvas ref="controlDouble" style="position: relative; background: #bef1fc" :id="canvas.id" :height="canvas.height" :width="canvas.width" ></canvas> </section> </template> <script> export default { data: () => ({ staticScale: 4, ctx: null, // getContext() 方法返回一个用于在画布上绘图的环境。 // canvas 画布相关信息 canvas: { id: null, // canvas id context: '2d', // 参数 contextID 指定了您想要在画布上绘制的类型 globalAlpha: 1, // canvas 透明度 width: window.innerWidth, // canvas 默认宽度 height: window.innerHeight,// canvas 默认高度 scale: 1, translate: { x: 0, y: 0 }, mapData: null }), props: { // 地图相关的数据,通过 ajax 从云端获取 map: { type: Object, default: () => { return { x_min: 0, y_min: 0, width: 0, // 地图的宽 height: 0, // 地图的高 resolution: 1, path: { // 机器人运行的路径 history: null, current: null }, data: null, // base64 格式的地图 isOpenDrag: true, // 是否可以拖拽 fitAuto: true, } } }, }, }; </script> <style scoped> * { margin: 0; padding: 0; box-sizing: border-box; } .canvasBox { position: relative; box-sizing: border-box; width: 100%; height: 100%; transform: scale(0.25); transform-origin: 0 0; } .baseCanvas { display: none; } </style>
-
接下我们对组件进行初始化操作,首先我们在 beforeMount 生命周期 对canvas的参数进行初始化。
beforeMount() { // 随机 canvas id this.canvas.id = getRandomId(); this.canvas.secondId = this.canvas.id + "_save"; }
这里我们看到 canvas 的 id 是通过一个随机函数去获取的,之所以要用随机的 id, 是因为这是个组件,我们可以在同一个页面多次使用该组件,因此使用固定 id 的话,在同一个页面就存在多个同一 id 的标签,这是不被允许的。
-
下一步我们就要对由父组件传进来的 map 进行处理了,即将 map 渲染到页面上。因地图数据是通过 ajax 请求获取的,所以在创建组件时props里的map参数是没有数据的。我们就不能在 mounted 生命周期里对地图进行初始化,需要通过监听器对 map 进行监听,当 map 的数据更新时才进行初始化。
watch: { map: { handle: function() { this.init() // init 为在methods里定义的初始化函数。 }, deep: true } }
-
接下来就是初始化函数了,函数均定义在 methods 中。
初始化canvas并对 地图数据进行预处理,并且将 base64 格式的数据装换为一维数组,数组的每一个元素对应了我们预设的不同颜色,可以简单的理解为数组的元素就是一种颜色。changeRobotMapDataToArray 函数定义在utils。
// 初始化入口 init() { // 初始化 canvas 的宽高, staticScale 为缩放比, this.canvas.width = this.map.width * this.staticScale; this.canvas.height = this.map.height * this.staticScale; this.ctx = this.getContext(this.canvas.id); // Canvas.getContext(contextID) 返回一个用于在画布上绘图的环境。 this.cleanCanvas(this.ctx); // 清除 canvas 上的画像 let data = changeRobotMapDataToArray(resource.map4); // 将 base64 格式的地图装换为一维数组 // 绘制canvas 入口函数 this.drawRobotMap(data, this.map.width, this.map.height) }
6.我们要将定义一维数组根据预设的颜色值转换为具有空间位置关系的单元格,可以简单的理解为像素格,只不过这里的像素格的宽高不确定,是根据缩放比来确定的。
// 绘制canvas 入口函数 drawRobotMap(data, w, h) { const fillStyleMap = { 0: "rgb(109,109,140)", 127: "rgba(0,0,0,0)", 255: "#ffffff", }; // 栅格矩形宽高 设置为1px const width = 1 * this.staticScale; const height = 1 * this.staticScale; let gridData = []; data.forEach((val, index) => { let x = 0; // 判断是否换行,以确定坐标X轴位置 if (index % w === 0) { x = 0; } else { x = index % w; } let y = h - Math.ceil(index / w); let fillStyleColor = "rgba(255,255,255,1)"; // 设置单元格的颜色 if (val > 0 && val <= 100) { fillStyleColor = this.getFillStyleColor(val); // getFillStyleColor 为匹配预设颜色的方法 } else { fillStyleColor = fillStyleMap[val] || "rgba(255,255,255,1)"; } // 具有空间位置关系的单元格 let point = { x, y, width, height, fillStyle: fillStyleColor, val, }; // 使用数组储存单元格 gridData.push(point); }, this); this.$nextTick(() => { // 开始绘制 this.drawGrid(this.ctx, gridData); }); }
7.数组已经转换为一个个单元格,并且存放在数组中,在接下来的步骤中,我们就将一个个单元格渲染到canvas上。
drawGrid(ctx, grids) { // 遍历数组, 将每一个单元格绘制到canvas上 for (let data of grids) { let opts = { fillStyle: "rgba(0,0,0,1)", width: 1, height: 1, x: 100, y: 100, }; if (data) { Object.assign(opts, data); } ctx.save(); ctx.fillStyle = opts.fillStyle; // Canvas.fillRect() 绘制矩形 https://www.runoob.com/jsref/met-canvas-fillrect.html ctx.fillRect( opts.x * this.staticScale, opts.y * this.staticScale, opts.width, opts.height ); // Canvas.restore() 方法将绘图状态置为保存值。 ctx.restore(); } }
8.地图基本就初始化完成了一个基础的地图,想要可以对地图进行拖拽缩放功能,这需要在canvas上添加事件监听并对事件进行处理。
<template> <section class="canvasBox" id="canvasBox"> <canvas ref="controlDouble" style="position: relative; background: #bef1fc" :id="canvas.id" :height="canvas.height" :width="canvas.width" @touchstart.stop="dragStart($event)" @touchmove.stop="dragMove($event)" @touchend.stop="dragEnd($event)" ></canvas> </section> </template>
总结
在云端请求的地图数据中地图的数据本身就是一张图片,相比于使用 canvas 的 drawImage 方法将图片直接画在 canvas 上,把图片的转换为一个个的像素格子再进行渲染,验证了地图上每一个点有准确的坐标,而且当用户在缩放地图的时候不会使地图变的模糊不清。
源码
更多原创内容请关注:中软国际 HarmonyOS技术团队
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。