vue使用canvas畫布實(shí)現(xiàn)平面圖點(diǎn)位標(biāo)注功能(最新推薦)
前言
- 最近需要一個在平面圖標(biāo)注點(diǎn)位功能,去搜了一圈,發(fā)現(xiàn)......,最后在查閱文檔一頓操作之后。
- 不停修改bug之后,做出一版可以基本使用的版本。
- 最后發(fā)現(xiàn)canvas標(biāo)簽可以完成很多功能,電子簽名,點(diǎn)位標(biāo)注,問題標(biāo)注,畫圖功能等等
效果

功能難點(diǎn)
畫布渲染問題-基于canvas提供的渲染方法封裝渲染方法,x,y坐標(biāo),width,height高,url圖片
移動距離問題-我們需要借助鼠標(biāo)點(diǎn)擊,移動,彈起來事件計(jì)算移動的距離,來更改x,y坐標(biāo)
選中圖標(biāo)問題-當(dāng)我們畫布上有多個圖標(biāo)時(shí)通過x,y,加上width,height和點(diǎn)擊時(shí)x,y坐標(biāo)判斷是那個圖標(biāo)在被點(diǎn)擊,在數(shù)組中找到匹配返回下標(biāo),反之就是點(diǎn)擊背景圖
畫布渲染問題-生成畫布之后,為了讓用戶無感操作,最好以幀方式刷新畫布(定時(shí)器方式)
圖標(biāo)數(shù)據(jù)格式-畫布上圖標(biāo)有很多個圖標(biāo),改變一個同時(shí),其他也是要跟著渲染
設(shè)備信息問題-我們需要在畫布上點(diǎn)擊獲取到圖標(biāo)的下標(biāo)之后,把x,y傳遞給信息框,顯示
自己理解
現(xiàn)在這個版本僅僅相當(dāng)于是一個例子,但是也是費(fèi)了不少時(shí)間和bug才艱辛完成的
為什么說是例子,可能還會有bug,適配,api交互,放大,存儲問題等等。
代碼實(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="請搜索樓層"
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">
請拖動圖標(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>序列號: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>序列號碼:{{ 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: "中國",
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,
//記錄需要移動的圖片的開光
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è)門旁邊",
},
],
// 圖標(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)前頁
pageNo: 1,
// 每頁條數(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),移動圖標(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
);
}
},
// 繪制移動的圖片
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)移動觸發(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("請拖動圖標(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)過這一趟流程下來相信你也對 vue-使用canvas畫布實(shí)現(xiàn)平面圖點(diǎn)位標(biāo)注功能 有了初步的深刻印象,但在實(shí)際開發(fā)中我 們遇到的情況肯定是不一樣的,所以我們要理解它的原理,萬變不離其宗。
到此這篇關(guān)于vue使用canvas畫布實(shí)現(xiàn)平面圖點(diǎn)位標(biāo)注功能的文章就介紹到這了,更多相關(guān)vue canvas平面圖點(diǎn)位標(biāo)注內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
前端vue+element使用SM4國密加密解密的詳細(xì)實(shí)例
國密即國家密碼局認(rèn)定的國產(chǎn)密碼算法,主要有SM1,SM2,SM3,SM4,下面這篇文章主要給大家介紹了關(guān)于前端vue+element使用SM4國密加密解密的相關(guān)資料,需要的朋友可以參考下2023-03-03
vue3利用store實(shí)現(xiàn)記錄滾動位置的示例
這篇文章主要介紹了vue3利用store實(shí)現(xiàn)記錄滾動位置的示例,幫助大家更好的理解和學(xué)習(xí)使用vue框架,感興趣的朋友可以了解下2021-04-04
@vue/cli4升級@vue/cli5?node.js?polyfills錯誤的解決方式
最近在升級vue/cli的具有了一些問題,解決問題的過程也花費(fèi)了些時(shí)間,所以下面這篇文章主要給大家介紹了關(guān)于@vue/cli4升級@vue/cli5?node.js?polyfills錯誤的解決方式,需要的朋友可以參考下2022-09-09
Vue實(shí)現(xiàn)表格數(shù)據(jù)的增刪改查的示例代碼
Vue是一個用于構(gòu)建用戶界面的JavaScript框架,在Vue中可以通過使用Vue組件來實(shí)現(xiàn)對表格的增刪改查操作,下面將介紹一些基礎(chǔ)的Vue組件和技術(shù)來實(shí)現(xiàn)對表格的增刪改查操作,需要的朋友可以參考下2024-08-08
elementui 開始結(jié)束時(shí)間可以選擇同一天不同時(shí)間段的實(shí)現(xiàn)代碼
這篇文章主要介紹了elementui 開始結(jié)束時(shí)間可以選擇同一天不同時(shí)間段的實(shí)現(xiàn)代碼,需要先在main.js中導(dǎo)入相應(yīng)代碼,代碼簡單易懂,需要的朋友可以參考下2024-02-02
vue keep-alive 動態(tài)刪除組件緩存的例子
今天小編就為大家分享一篇vue keep-alive 動態(tài)刪除組件緩存的例子,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11

