vue使用canvas畫布實現(xiàn)平面圖點位標注功能(最新推薦)
前言
- 最近需要一個在平面圖標注點位功能,去搜了一圈,發(fā)現(xiàn)......,最后在查閱文檔一頓操作之后。
- 不停修改bug之后,做出一版可以基本使用的版本。
- 最后發(fā)現(xiàn)canvas標簽可以完成很多功能,電子簽名,點位標注,問題標注,畫圖功能等等
效果
功能難點
畫布渲染問題-基于canvas提供的渲染方法封裝渲染方法,x,y坐標,width,height高,url圖片
移動距離問題-我們需要借助鼠標點擊,移動,彈起來事件計算移動的距離,來更改x,y坐標
選中圖標問題-當(dāng)我們畫布上有多個圖標時通過x,y,加上width,height和點擊時x,y坐標判斷是那個圖標在被點擊,在數(shù)組中找到匹配返回下標,反之就是點擊背景圖
畫布渲染問題-生成畫布之后,為了讓用戶無感操作,最好以幀方式刷新畫布(定時器方式)
圖標數(shù)據(jù)格式-畫布上圖標有很多個圖標,改變一個同時,其他也是要跟著渲染
設(shè)備信息問題-我們需要在畫布上點擊獲取到圖標的下標之后,把x,y傳遞給信息框,顯示
自己理解
現(xiàn)在這個版本僅僅相當(dāng)于是一個例子,但是也是費了不少時間和bug才艱辛完成的
為什么說是例子,可能還會有bug,適配,api交互,放大,存儲問題等等。
代碼實現(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="請搜索樓層" 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> <!-- 點位模塊 --> <el-col :xs="24" :sm="15" :lg="15"> <div class="grid-right"> <!-- 點位標題 --> <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"> 請拖動圖標到安裝位置-<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> <!-- 圖標提示信息 --> <el-popover placement="top" id="popovercan" width="200" v-model="canvasvisible" > <div class="popover-top"> <i>傳感器設(shè)備</i> </div> <p>序列號: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"> <!-- 標題 --> <div class="grid-top"> <i></i> <p>配置資源點-點擊圖標加載到畫布中</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>序列號碼:{{ item.phone }}</p> <p>設(shè)備類型:{{ item.newtype }}</p> <p>詳細位置:{{ 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: "中國", 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標簽 canvas: null, // 創(chuàng)建畫布 ctx: null, // 畫布大小 canvasWidth: 970, canvasHeight: 500, //定時器 intervalId: null, //判斷鼠標是否點擊 isClick: false, //記錄需要移動的圖片的開光 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: "中國北京市北京人名大會堂側(cè)門旁邊", }, ], // 圖標數(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)前頁 pageNo: 1, // 每頁條數(shù) pageSize: 10, }, clickicon: {}, canvasvisible: false, }; }, created() {}, methods: { // 樹結(jié)構(gòu)點擊事件 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é)點搜索 filterNode(value, data) { if (!value) return true; return data.name.indexOf(value) !== -1; }, // 確認保存按鈕 save() { this.canvasinit = true; this.init(); }, // 創(chuàng)建畫布 init() { // 找到畫布標簽 this.canvas = this.$refs.canvas; this.ctx = this.canvas.getContext("2d"); // 創(chuàng)建背景,圖標,移動圖標 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(); }; }, //加載圖標 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 ); } }, // 繪制移動的圖片 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); }, //判斷鼠標是否在圖標范圍內(nèi),并返回下標 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; }, // 定時器刷新畫布 dataRefreh() { if (this.intervalId != null) { return; } this.intervalId = setInterval(() => { this.loadBgImg(); }, this.frameNumber); }, //鼠標點擊觸發(fā)事件 canvasMouseDown(e) { console.log("鼠標點擊", 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; }); }, //鼠標移動觸發(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; } }, //鼠標抬起觸發(fā)事件 canvasMouseUp(e) { console.log("執(zhí)行了"); this.isClick = false; }, handleClick(item) { // 判斷是否上傳樓層圖片 // 創(chuàng)建點位 let imgs = {}; imgs.url = this.Icondata; imgs.x = 0; imgs.y = 0; imgs.width = 46; imgs.height = 46; // 加載點位圖標 this.canvasSensorImg.push(imgs); this.$message.success("請拖動圖標到指定點位"); }, }, 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; // 標題 .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)過這一趟流程下來相信你也對 vue-使用canvas畫布實現(xiàn)平面圖點位標注功能 有了初步的深刻印象,但在實際開發(fā)中我 們遇到的情況肯定是不一樣的,所以我們要理解它的原理,萬變不離其宗。
到此這篇關(guān)于vue使用canvas畫布實現(xiàn)平面圖點位標注功能的文章就介紹到這了,更多相關(guān)vue canvas平面圖點位標注內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
@vue/cli4升級@vue/cli5?node.js?polyfills錯誤的解決方式
最近在升級vue/cli的具有了一些問題,解決問題的過程也花費了些時間,所以下面這篇文章主要給大家介紹了關(guān)于@vue/cli4升級@vue/cli5?node.js?polyfills錯誤的解決方式,需要的朋友可以參考下2022-09-09Vue實現(xiàn)表格數(shù)據(jù)的增刪改查的示例代碼
Vue是一個用于構(gòu)建用戶界面的JavaScript框架,在Vue中可以通過使用Vue組件來實現(xiàn)對表格的增刪改查操作,下面將介紹一些基礎(chǔ)的Vue組件和技術(shù)來實現(xiàn)對表格的增刪改查操作,需要的朋友可以參考下2024-08-08elementui 開始結(jié)束時間可以選擇同一天不同時間段的實現(xiàn)代碼
這篇文章主要介紹了elementui 開始結(jié)束時間可以選擇同一天不同時間段的實現(xiàn)代碼,需要先在main.js中導(dǎo)入相應(yīng)代碼,代碼簡單易懂,需要的朋友可以參考下2024-02-02vue keep-alive 動態(tài)刪除組件緩存的例子
今天小編就為大家分享一篇vue keep-alive 動態(tài)刪除組件緩存的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11