vue使用canvas畫布實(shí)現(xiàn)平面圖點(diǎn)位標(biāo)注功能(最新推薦)
前言
- 最近需要一個(gè)在平面圖標(biāo)注點(diǎn)位功能,去搜了一圈,發(fā)現(xiàn)......,最后在查閱文檔一頓操作之后。
- 不停修改bug之后,做出一版可以基本使用的版本。
- 最后發(fā)現(xiàn)canvas標(biāo)簽可以完成很多功能,電子簽名,點(diǎn)位標(biāo)注,問(wèn)題標(biāo)注,畫圖功能等等
效果
功能難點(diǎn)
畫布渲染問(wèn)題-基于canvas提供的渲染方法封裝渲染方法,x,y坐標(biāo),width,height高,url圖片
移動(dòng)距離問(wèn)題-我們需要借助鼠標(biāo)點(diǎn)擊,移動(dòng),彈起來(lái)事件計(jì)算移動(dòng)的距離,來(lái)更改x,y坐標(biāo)
選中圖標(biāo)問(wèn)題-當(dāng)我們畫布上有多個(gè)圖標(biāo)時(shí)通過(guò)x,y,加上width,height和點(diǎn)擊時(shí)x,y坐標(biāo)判斷是那個(gè)圖標(biāo)在被點(diǎn)擊,在數(shù)組中找到匹配返回下標(biāo),反之就是點(diǎn)擊背景圖
畫布渲染問(wèn)題-生成畫布之后,為了讓用戶無(wú)感操作,最好以幀方式刷新畫布(定時(shí)器方式)
圖標(biāo)數(shù)據(jù)格式-畫布上圖標(biāo)有很多個(gè)圖標(biāo),改變一個(gè)同時(shí),其他也是要跟著渲染
設(shè)備信息問(wèn)題-我們需要在畫布上點(diǎn)擊獲取到圖標(biāo)的下標(biāo)之后,把x,y傳遞給信息框,顯示
自己理解
現(xiàn)在這個(gè)版本僅僅相當(dāng)于是一個(gè)例子,但是也是費(fèi)了不少時(shí)間和bug才艱辛完成的
為什么說(shuō)是例子,可能還會(huì)有bug,適配,api交互,放大,存儲(chǔ)問(wèn)題等等。
代碼實(shí)現(xiàn)-可以直接復(fù)制
<template> <div class="app-container"> <!-- 側(cè)邊欄 --> <el-row :gutter="20"> <!-- 樹結(jié)構(gòu) --> <el-col :xs="24" :sm="4" :lg="4"> <div class="grid-left"> <!-- 篩選框 --> <el-input class="searchinput" placeholder="請(qǐng)搜索樓層" suffix-icon="el-input__icon el-icon-search" v-model="treeinput" clearable size="small" ></el-input> <!-- 樹結(jié)構(gòu) --> <el-tree :data="treedata" :props="defaultProps" @node-click="handleNodeClick" ref="menuTree" node-key="id" default-expand-all :filter-node-method="filterNode" ></el-tree> </div> </el-col> <!-- 點(diǎn)位模塊 --> <el-col :xs="24" :sm="15" :lg="15"> <div class="grid-right"> <!-- 點(diǎn)位標(biāo)題 --> <div class="grid-top"></div> <!-- 圖片展示 --> <img class="bigImg" :src="backpicture" v-if="backpicture" /> <!-- 生成畫布模塊 --> <div class="canvas-box" v-show="this.canvasinit"> <!-- 表頭 --> <div class="canvas-title"> <div class="zuo"> 請(qǐng)拖動(dòng)圖標(biāo)到安裝位置-<i>廠家平面圖 平面圖</i> </div> </div> <!-- 畫布 --> <canvas ref="canvas" width="970" height="500" @mousedown="canvasMouseDown" @mousemove="canvasMouseMove" @mouseup="canvasMouseUp" ></canvas> </div> <!-- 保存平面圖 --> <div class="bottom" v-if="!this.canvasinit"> <el-button type="primary" @click="save">保存</el-button> </div> <!-- 圖標(biāo)提示信息 --> <el-popover placement="top" id="popovercan" width="200" v-model="canvasvisible" > <div class="popover-top"> <i>傳感器設(shè)備</i> </div> <p>序列號(hào):sjhdkjshkj</p> <p>設(shè)備類型:是給大家灰色軌跡</p> <p></p> </el-popover> </div> </el-col> <!-- 設(shè)備信息 --> <el-col :xs="24" :sm="5" :lg="5"> <div class="grid-table"> <!-- 標(biāo)題 --> <div class="grid-top"> <i></i> <p>配置資源點(diǎn)-點(diǎn)擊圖標(biāo)加載到畫布中</p> </div> <!-- 表格數(shù)據(jù) --> <div class="newtable"> <div class="new-item" v-for="item in tableData" :key="item.id" @click="handleClick(item)" > <img :src="item.img" alt="" /> <div class="newconter"> <p>序列號(hào)碼:{{ item.phone }}</p> <p>設(shè)備類型:{{ item.newtype }}</p> <p>詳細(xì)位置:{{ item.sys }}</p> </div> </div> </div> <div class="pagination"> <el-pagination small :page-size="pageInfo.pageSize" layout="prev, pager, next" :total="pageInfo.total" > </el-pagination> </div> </div> </el-col> </el-row> </div> </template> ? <script> export default { name: "Ceshi", watch: { treeinput(val) { this.$refs.menuTree.filter(val); }, }, data() { return { // 樹結(jié)構(gòu)篩選框 treeinput: "", // 樹結(jié)構(gòu)數(shù)據(jù) treedata: [ { id: 1, name: "中國(guó)", children: [ { id: 1, name: "廣東", children: [ { id: 4, name: "惠州", }, { id: 5, name: "深圳", }, { id: 6, name: "廣州", }, ], }, { id: 2, name: "湖北", children: [ { id: 7, name: "武漢", }, ], }, { id: 3, name: "北京", }, ], }, ], // 樹結(jié)構(gòu)配置 // 樹形數(shù)據(jù)分析 defaultProps: { id: "id", label: "name", children: "children", }, // 獲取canvas標(biāo)簽 canvas: null, // 創(chuàng)建畫布 ctx: null, // 畫布大小 canvasWidth: 970, canvasHeight: 500, //定時(shí)器 intervalId: null, //判斷鼠標(biāo)是否點(diǎn)擊 isClick: false, //記錄需要移動(dòng)的圖片的開光 index: -1, frameNumber: 20, sensorImgList: [], backgroundImg: { url: "https://img2.baidu.com/it/u=2832413337,2216208892&fm=253&fmt=auto&app=138&f=JPEG?w=544&h=500", x: 0, y: 0, width: 970, height: 500, }, canvasSensorImg: [ { channelId: 12, height: 46, url: "https://img2.baidu.com/it/u=3823882177,3352315913&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", width: 46, x: 247, y: 233, }, { channelId: 13, height: 46, url: "https://img2.baidu.com/it/u=3823882177,3352315913&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", width: 46, x: 400, y: 400, }, ], tableData: [ { id: 1, img: "https://img2.baidu.com/it/u=3823882177,3352315913&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", phone: "865566043823044", newtype: "NP-FDY100-N", sys: "中國(guó)北京市北京人名大會(huì)堂側(cè)門旁邊", }, ], // 圖標(biāo)數(shù)據(jù) Icondata: "https://img2.baidu.com/it/u=3823882177,3352315913&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", // 背景圖圖片 backpicture: "https://img2.baidu.com/it/u=2832413337,2216208892&fm=253&fmt=auto&app=138&f=JPEG?w=544&h=500", // 畫布開關(guān) canvasinit: false, // 設(shè)備抱緊查詢 pageInfo: { // 總條數(shù) total: 17, // 當(dāng)前頁(yè) pageNo: 1, // 每頁(yè)條數(shù) pageSize: 10, }, clickicon: {}, canvasvisible: false, }; }, created() {}, methods: { // 樹結(jié)構(gòu)點(diǎn)擊事件 handleNodeClick(data) { if (data.children.length !== 0) { return; } console.log("樹形結(jié)構(gòu)", data); console.log(data.id); // 樓層id this.page.floorId = data.id; }, // 樹節(jié)點(diǎn)搜索 filterNode(value, data) { if (!value) return true; return data.name.indexOf(value) !== -1; }, // 確認(rèn)保存按鈕 save() { this.canvasinit = true; this.init(); }, // 創(chuàng)建畫布 init() { // 找到畫布標(biāo)簽 this.canvas = this.$refs.canvas; this.ctx = this.canvas.getContext("2d"); // 創(chuàng)建背景,圖標(biāo),移動(dòng)圖標(biāo) this.loadBgImg(); // 刷新畫布 this.dataRefreh(); }, loadBgImg() { let img = new Image(); let bgImg = this.backgroundImg; img.src = bgImg.url; img.onload = () => { this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); this.ctx.drawImage(img, bgImg.x, bgImg.y, bgImg.width, bgImg.height); this.drawCanvasSensorImg(); this.loadSensorImg(); }; }, //加載圖標(biāo) loadSensorImg() { let imgList = []; for (let i = 0; i < this.sensorImgList.length; i++) { let img = new Image(); let sensorImg = this.sensorImgList[i]; img.src = sensorImg.url; let imgs = {}; imgs.img = img; imgs.x = sensorImg.x; imgs.y = sensorImg.y; imgs.width = sensorImg.width; imgs.height = sensorImg.height; // console.log(imgs) imgList.push(imgs); } this.drawImg(imgList); }, //繪制圖片方法 drawImg(imgList) { for (let i = 0; i < imgList.length; i++) { this.ctx.drawImage( imgList[i].img, imgList[i].x, imgList[i].y, imgList[i].width, imgList[i].height ); } }, // 繪制移動(dòng)的圖片 drawCanvasSensorImg() { let imgList = []; for (let i = 0; i < this.canvasSensorImg.length; i++) { let img = new Image(); let sensorImg = this.canvasSensorImg[i]; img.src = sensorImg.url; let imgs = {}; imgs.img = img; imgs.x = sensorImg.x; imgs.y = sensorImg.y; imgs.width = sensorImg.width; imgs.height = sensorImg.height; imgList.push(imgs); } this.drawImg(imgList); }, //判斷鼠標(biāo)是否在圖標(biāo)范圍內(nèi),并返回下標(biāo) isMouseInIcon(e, imgList) { let x = e.offsetX; let y = e.offsetY; for (let i = 0; i < imgList.length; i++) { let imgX = imgList[i].x; let imgY = imgList[i].y; let imgWidth = imgList[i].width; let imgHeight = imgList[i].height; if ( x > imgX && x < imgX + imgWidth && y > imgY && y < imgY + imgHeight ) { return i; } } return -1; }, // 定時(shí)器刷新畫布 dataRefreh() { if (this.intervalId != null) { return; } this.intervalId = setInterval(() => { this.loadBgImg(); }, this.frameNumber); }, //鼠標(biāo)點(diǎn)擊觸發(fā)事件 canvasMouseDown(e) { console.log("鼠標(biāo)點(diǎn)擊", e); this.isClick = true; this.index = this.isMouseInIcon(e, this.canvasSensorImg); ? if (this.index == -1) { console.log("沒選中"); return; } this.$nextTick(() => { console.log("top"); const canpro = document.getElementById("popovercan"); canpro.style.position = "absolute"; canpro.style.top = this.canvasSensorImg[this.index].y + 40 + "px"; canpro.style.left = this.canvasSensorImg[this.index].x - 60 + "px"; this.canvasvisible = !this.canvasvisible; }); }, //鼠標(biāo)移動(dòng)觸發(fā)事件 canvasMouseMove(e) { if (!this.isClick) { return; } if (this.index != -1) { this.canvasvisible = false; let x = e.offsetX; let y = e.offsetY; this.canvasSensorImg[this.index].x = this.canvasSensorImg[this.index].x < 0 ? 0 : x - this.canvasSensorImg[this.index].width / 2; this.canvasSensorImg[this.index].y = this.canvasSensorImg[this.index].y < 0 ? 0 : y - this.canvasSensorImg[this.index].height / 2; } }, //鼠標(biāo)抬起觸發(fā)事件 canvasMouseUp(e) { console.log("執(zhí)行了"); this.isClick = false; }, handleClick(item) { // 判斷是否上傳樓層圖片 // 創(chuàng)建點(diǎn)位 let imgs = {}; imgs.url = this.Icondata; imgs.x = 0; imgs.y = 0; imgs.width = 46; imgs.height = 46; // 加載點(diǎn)位圖標(biāo) this.canvasSensorImg.push(imgs); this.$message.success("請(qǐng)拖動(dòng)圖標(biāo)到指定點(diǎn)位"); }, }, beforeDestroy() { clearInterval(this.intervalId); this.intervalId = null; }, }; </script> ? <style lang="scss" scoped> .app-container { height: 815px; .grid-left { padding: 30px 20px 20px; min-height: 815px; border: 1px solid #ccc; // 輸入框 .searchinput { margin-bottom: 20px; } } .grid-right { // width: 1363px; width: 100%; height: 815px; padding: 30px 20px 20px; border: 1px solid #ccc; position: relative; // 標(biāo)題 .grid-top { width: 100%; } // 圖片展示 .bigImg { display: block; width: 970px; height: 500px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -54%); } // 畫布模塊 .canvas-box { width: 1000px; height: 620px; padding: 10px; background-color: #fff; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -55%); cursor: pointer; .canvas-title { display: flex; // background-color: skyblue; align-items: center; justify-content: space-between; margin-bottom: 10px; } } // 保存平面圖 .bottom { width: 95%; height: 100px; border-top: 1px solid #ccc; display: flex; justify-content: center; align-items: center; // background-color: skyblue; position: absolute; left: 50%; transform: translateX(-50%); bottom: 0; } // 設(shè)備彈出框 ::v-deep .el-popover { // background-color: skyblue; padding: 0; .popover-top { display: flex; justify-content: space-between; align-items: center; .popover-title { width: 30px; height: 35px; cursor: pointer; background-color: #e72528; display: flex; justify-content: center; align-items: center; border-radius: 0 5px 0 0; i { font-size: 25px; } } i { font-style: normal; // font-size: 20px; } } p { margin: 3px 0; } } } .grid-table { padding: 30px 5px 5px; height: 815px; border: 1px solid #ccc; .grid-top { display: flex; align-items: center; // background-color: skyblue; i { display: block; width: 3px; height: 19px; background-color: #409eff; // border-radius: 2px; margin-right: 5px; } p { // font-size: 17px; // font-weight: 700; color: rgb(36, 37, 37); } } // 設(shè)備列表 .newtable { background-color: #fff; .new-item { display: flex; justify-content: space-between; align-items: center; height: 80px; padding: 0 5px; cursor: pointer; // background-color: skyblue; border-top: 2px solid #c8c8c8; // border-bottom: 2px solid #c8c8c8; &:last-child { border-bottom: 2px solid #c8c8c8; } img { width: 45px; height: 45px; } .newconter { margin-left: 10px; font-size: 12px; p { padding: 0; margin: 2px 0; } } } } .pagination { margin-top: 10px; display: flex; justify-content: center; align-content: center; } } } </style>
總結(jié):
經(jīng)過(guò)這一趟流程下來(lái)相信你也對(duì) vue-使用canvas畫布實(shí)現(xiàn)平面圖點(diǎn)位標(biāo)注功能 有了初步的深刻印象,但在實(shí)際開發(fā)中我 們遇到的情況肯定是不一樣的,所以我們要理解它的原理,萬(wàn)變不離其宗。
到此這篇關(guān)于vue使用canvas畫布實(shí)現(xiàn)平面圖點(diǎn)位標(biāo)注功能的文章就介紹到這了,更多相關(guān)vue canvas平面圖點(diǎn)位標(biāo)注內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
前端vue+element使用SM4國(guó)密加密解密的詳細(xì)實(shí)例
國(guó)密即國(guó)家密碼局認(rèn)定的國(guó)產(chǎn)密碼算法,主要有SM1,SM2,SM3,SM4,下面這篇文章主要給大家介紹了關(guān)于前端vue+element使用SM4國(guó)密加密解密的相關(guān)資料,需要的朋友可以參考下2023-03-03在IDEA中配置eslint和prettier的全過(guò)程
日常開發(fā)中,建議用可以用Prettier做代碼格式化,然后用eslint做校驗(yàn),下面這篇文章主要給大家介紹了關(guān)于在IDEA中配置eslint和prettier的相關(guān)資料,需要的朋友可以參考下2024-02-02vue3利用store實(shí)現(xiàn)記錄滾動(dòng)位置的示例
這篇文章主要介紹了vue3利用store實(shí)現(xiàn)記錄滾動(dòng)位置的示例,幫助大家更好的理解和學(xué)習(xí)使用vue框架,感興趣的朋友可以了解下2021-04-04@vue/cli4升級(jí)@vue/cli5?node.js?polyfills錯(cuò)誤的解決方式
最近在升級(jí)vue/cli的具有了一些問(wèn)題,解決問(wèn)題的過(guò)程也花費(fèi)了些時(shí)間,所以下面這篇文章主要給大家介紹了關(guān)于@vue/cli4升級(jí)@vue/cli5?node.js?polyfills錯(cuò)誤的解決方式,需要的朋友可以參考下2022-09-09Vue實(shí)現(xiàn)表格數(shù)據(jù)的增刪改查的示例代碼
Vue是一個(gè)用于構(gòu)建用戶界面的JavaScript框架,在Vue中可以通過(guò)使用Vue組件來(lái)實(shí)現(xiàn)對(duì)表格的增刪改查操作,下面將介紹一些基礎(chǔ)的Vue組件和技術(shù)來(lái)實(shí)現(xiàn)對(duì)表格的增刪改查操作,需要的朋友可以參考下2024-08-08elementui 開始結(jié)束時(shí)間可以選擇同一天不同時(shí)間段的實(shí)現(xiàn)代碼
這篇文章主要介紹了elementui 開始結(jié)束時(shí)間可以選擇同一天不同時(shí)間段的實(shí)現(xiàn)代碼,需要先在main.js中導(dǎo)入相應(yīng)代碼,代碼簡(jiǎn)單易懂,需要的朋友可以參考下2024-02-02vue keep-alive 動(dòng)態(tài)刪除組件緩存的例子
今天小編就為大家分享一篇vue keep-alive 動(dòng)態(tài)刪除組件緩存的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11