VUE?使用canvas繪制管線管廊示例詳解
引言

上節(jié)說(shuō)完了基本的實(shí)現(xiàn)思路,那今天我將展開(kāi)來(lái)說(shuō)一下具體的實(shí)現(xiàn)方法。本節(jié)主要是處理每一個(gè)繪制是如何進(jìn)行的。
那就直接開(kāi)始吧。
首先,我在JS中定義了一下常量,其作用如下:
let canvas = {}, ctx = {};
const image = new Image();
// 定義管線默認(rèn)寬度
const PIPELINE_NUMBER = 15;
// 定義圖標(biāo)默認(rèn)縮放比例
const ICON_SCALE_RATIO = 25;
// 所有繪制元素
let allElementCollection = [];
// 初始化管線水類型: 0為冷水 1為熱水
let pipeline_water_type = 0;
// 當(dāng)前繪制設(shè)備的對(duì)象
let equipment_select = {};
// 是否顯示設(shè)備繪制的范圍
let equipment_area_show = false
// 初始化繪制類型:0為管線 1為設(shè)備,2為文字框 默認(rèn)為管線
let draw_element_type = 0;
// 管線流動(dòng)速度初始值
let pipeline_offset = 0;
// 定義當(dāng)前選中的已繪制元素
let current_select_element_index = {};
接下來(lái)是處理鼠標(biāo)左鍵在按下后,在上節(jié)的代碼中,針對(duì)于鼠標(biāo)左鍵按下后,需要判斷當(dāng)前點(diǎn)擊的區(qū)域是否已經(jīng)存在繪制的內(nèi)容,如果有,執(zhí)行繪制內(nèi)容移動(dòng),如果沒(méi)有,則開(kāi)始新建繪制內(nèi)容,代碼如下:
if (shape) {
moveAllElement(e, clickX, clickY, rect, shape);
canvas.style.cursor= "move";
} else {
if (e.buttons === 1) {
draw_element_type === 0 ? drawRealTimePipeline(e, clickX, clickY, rect) : (draw_element_type === 1 ? drawRealTimeEquipment(e, clickX, clickY, rect) : drawRealTimeText(e, clickX, clickY, rect))
}
}
我們一個(gè)一個(gè)的來(lái),如果繪制內(nèi)容不存在,則執(zhí)行新建繪制內(nèi)容,這里我使用三目運(yùn)算來(lái)判斷當(dāng)前需要繪制的類型drawRealTimePipeline、drawRealTimeEquipment、drawRealTimeText。
drawRealTimePipeline:繪制實(shí)時(shí)管線
// 繪制實(shí)時(shí)管線
const drawRealTimePipeline = (e, clickX, clickY, rect) => {
const shape = new ElementFactory(clickX, clickY);
shape.endX = clickX;
shape.endY = clickY;
// 繪制管線時(shí),刪除通過(guò) new 的對(duì)象的 textInfo 和 equipmentInfo,這兩個(gè)對(duì)于管線來(lái)說(shuō)沒(méi)有用處
delete shape.textInfo;
delete shape.equipmentInfo;
let shapeWidth = 0, shapeHeight = 0;
// 為了方便處理元素刪除,動(dòng)態(tài)添加一個(gè)隨機(jī)的 ID,并且在當(dāng)前位置拿到,方便在繪制線段過(guò)短時(shí)來(lái)精確刪除
let current_uid = setuid(shape);
allElementCollection.push(shape);
window.onmousemove = (evt) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
shapeWidth = (evt.clientX - rect.left) - clickX;
shapeHeight = (evt.clientY - rect.top) - clickY;
// 判斷繪制為 豎線 還是 橫線
let shapeDirection = Math.abs(shapeWidth) >= Math.abs(shapeHeight);
if (shapeDirection) {
// 如果是橫線,則 endY 為固定值
shape.endX = evt.clientX - rect.left;
shape.endY = clickY + PIPELINE_NUMBER;
} else {
// 如果是豎線,則 endX 為固定值
shape.endX = clickX + PIPELINE_NUMBER;
shape.endY = evt.clientY - rect.top;
}
shape.pipelineInfo.direction = shapeDirection;
shape.pipelineInfo.waterType = pipeline_water_type;
draw();
};
// 畫(huà)線時(shí),鼠標(biāo)抬起判斷如果線段繪制過(guò)短,則不推入 allElementCollection
window.onmouseup = () => {
if(parseInt(draw_element_type) === 0 && shape.endX) {
if (Math.abs(shape.startX - shape.endX) < 45 && Math.abs(shape.startY - shape.endY) < 45) {
let index = allElementCollection.findIndex(item => item.uid === current_uid);
allElementCollection.splice(index, 1)
ctx.clearRect(0, 0, canvas.width, canvas.height)
draw()
}
}
};
}
const setuid = (shape) => {
// 生成唯一ID
let uid = Math.round( Math.random() * 100000000000);
shape.uid = uid;
return uid
}
drawRealTimeEquipment:繪制實(shí)時(shí)設(shè)備:
繪制設(shè)備時(shí),由于繪制的圖片,所以對(duì)于構(gòu)造函數(shù)中的endX、endY需要自己計(jì)算

// 繪制實(shí)時(shí)設(shè)備
const drawRealTimeEquipment = (e, clickX, clickY, rect) => {
const shape = new ElementFactory(clickX, clickY)
// 繪制設(shè)備時(shí),刪除通過(guò) new 的對(duì)象的 textInfo 和 pipelineInfo,這兩個(gè)對(duì)于圖形來(lái)說(shuō)沒(méi)有用處
delete shape.textInfo;
delete shape.pipelineInfo;
// 設(shè)備繪制在鼠標(biāo)點(diǎn)擊的那一刻就需要開(kāi)始創(chuàng)建,
setEquipment(e);
setuid(shape);
allElementCollection.push(shape);
window.onmousemove = (evt) => setEquipment(evt);
function setEquipment(evt) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
shape.startX = evt.clientX - rect.left;
shape.startY = evt.clientY - rect.top;
// 計(jì)算當(dāng)前繪制的endX endY
image.src = require(`../assets/images/${equipment_select.iconPath}`);
let icon_width = Math.ceil(image.width / ICON_SCALE_RATIO),
icon_height = Math.ceil(image.height / ICON_SCALE_RATIO);
shape.endX = evt.clientX - rect.left + icon_width;
shape.endY = evt.clientY - rect.top + icon_height;
draw();
}
draw();
};
drawRealTimeText:繪制實(shí)時(shí)文本:
// 繪制實(shí)時(shí)文字
const drawRealTimeText = (e, clickX, clickY, rect) => {
const shape = new ElementFactory(clickX, clickY);
setuid(shape);
// 繪制文字時(shí),刪除通過(guò) new 的對(duì)象的 equipmentInfo 和 pipelineInfo,這兩個(gè)對(duì)于圖形來(lái)說(shuō)沒(méi)有用處
delete shape.equipmentInfo;
delete shape.pipelineInfo;
ctx.font = `normal normal normal ${shape.textInfo.fontSize + 'px' || '16px'} Microsoft YaHei`;
const defaultText = '默認(rèn)文字,請(qǐng)右鍵修改';
const measureText = ctx.measureText(defaultText);
const textW = measureText.width,
textH = measureText.actualBoundingBoxAscent + measureText.actualBoundingBoxDescent;
shape.textInfo.text = defaultText;
allElementCollection.push(shape);
setText(e)
window.onmousemove = (evt) => setText(evt)
function setText(evt) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
shape.startX = evt.clientX - rect.left;
shape.startY = evt.clientY - rect.top;
shape.endX = evt.clientX - rect.left + textW;
shape.endY = evt.clientY - rect.top - textH;
draw();
}
draw();
};
接下來(lái)是鼠標(biāo)點(diǎn)的位置,已經(jīng)存在了繪制的內(nèi)容,那么這個(gè)時(shí)候就有兩種情況了,一個(gè)是 鼠標(biāo)左鍵 點(diǎn)擊的,一個(gè)是 鼠標(biāo)右鍵 點(diǎn)擊的。
鼠標(biāo)左鍵點(diǎn)擊只能執(zhí)行移動(dòng),鼠標(biāo)右鍵則是彈出操作框,如圖:



這里有一點(diǎn)需要注意,管線的操作框中沒(méi)有 編輯 按鈕,且彈出框的位置需要根據(jù)鼠標(biāo)點(diǎn)擊的位置變化而變化。
moveAllElement:元素移動(dòng)事件:
// 元素移動(dòng)
const moveAllElement = (e, clickX, clickY, rect, shape) => {
const { startX, startY, endX, endY } = shape;
let tipX = 0, tipY = 0;
// 鼠標(biāo)左鍵:拖動(dòng)位置
if (e.buttons === 1) {
window.onmousemove = (evt) => {
removeEditTip();
ctx.clearRect(0, 0, canvas.width, canvas.height);
const distanceX = evt.clientX - rect.left - clickX;
const distanceY = evt.clientY - rect.top - clickY;
shape.startX = startX + distanceX;
shape.startY = startY + distanceY;
shape.endX = endX + distanceX;
shape.endY = endY + distanceY;
draw();
}
}
// 鼠標(biāo)右鍵:執(zhí)行信息編輯
if (e.buttons === 2) {
if (shape.type === 0) {
// 管線
tipX = e.clientX;
tipY = e.clientY + 10;
} else if (shape.type === 1) {
// 如果點(diǎn)擊的是圖標(biāo),彈出提示出現(xiàn)在圖標(biāo)下方
tipX = (shape.endX - shape.startX) / 2 + shape.startX + rect.left
tipY = shape.endY + rect.top
} else if (shape.type === 2) {
// 文字
tipX = shape.startX + rect.left + ctx.measureText(`${shape.textInfo.text}`).width / 2;
tipY = shape.startY + rect.top;
}
createEditTip(tipX, tipY, shape);
return false
}
};
createEditTip為動(dòng)態(tài)創(chuàng)建的DOM結(jié)構(gòu),即操作提示框。
createEditTip、removeEditTip:動(dòng)態(tài)創(chuàng)建及移除DOM操作提示框:
// 創(chuàng)建管線點(diǎn)擊事件彈窗
const createEditTip = (x, y, shape) => {
let width = shape.type ? 180 : 120, marginLeft = shape.type ? 95 : 65, display = shape.type ? 'inline-block' : 'none'
removeEditTip()
let tipText = document.createElement('div')
tipText.classList.add('tip-text-content')
tipText.innerHTML = `<div class="tip-text" id="tipText" style="top: ${y + 10}px;left: ${x}px; width: ${width}px; margin-left:-${marginLeft}px; ">
<p>
<span id="equipmentDelete">刪除</span>
<span id="${parseInt(shape.type) === 2 ? 'textModify' : 'equipmentModify'}" style="display: ${display}">編輯</span>
<span id="buttonCancel">取消</span>
</p>
</div>`
document.body.appendChild(tipText)
document.getElementById('equipmentDelete').onclick = () => {
allElementCollection.splice(current_select_element_index, 1)
ctx.clearRect(0, 0, canvas.width, canvas.height)
draw()
removeEditTip()
};
// 判斷點(diǎn)擊的是 圖片 的編輯按鈕,還是 文字 的編輯按鈕
let modifyButton = document.getElementById('equipmentModify') ? 'equipmentModify' : 'textModify';
document.getElementById(modifyButton).onclick = () => {
removeEditTip()
};
document.getElementById('buttonCancel').onclick = () => {
removeEditTip()
};
};
// 移除管線事件彈窗
const removeEditTip = () => {
const popup = document.querySelector('.tip-text-content')
if (popup) document.body.removeChild(popup)
}
寫(xiě)在最后,必看
本節(jié)主要介紹看了如何動(dòng)態(tài)的去 創(chuàng)建繪制 內(nèi)容的對(duì)象,所有實(shí)現(xiàn)繪制效果的方法均在 每個(gè) new出來(lái)的shape 對(duì)象中,我們只需要循環(huán)調(diào)用每個(gè)對(duì)象的 繪制方法 即可,在本節(jié)中未涉及。
本節(jié)先到此為止,下節(jié)我會(huì)將 構(gòu)造函數(shù) 中的各種繪制方法進(jìn)行完善,并且會(huì)詳細(xì)的說(shuō)一下如何將 allElementCollection 中的對(duì)象來(lái)繪制到canvas界面中。
以上就是VUE 使用canvas繪制管線/管廊的詳細(xì)內(nèi)容,更多關(guān)于VUE canvas繪制管線/管廊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Vue.js開(kāi)發(fā)微信小程序開(kāi)源框架mpvue解析
這篇文章主要介紹了使用Vue.js開(kāi)發(fā)微信小程序開(kāi)源框架mpvue解析,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
vue項(xiàng)目關(guān)閉eslint校驗(yàn)
eslint是一個(gè)JavaScript的校驗(yàn)插件,通常用來(lái)校驗(yàn)語(yǔ)法或代碼的書(shū)寫(xiě)風(fēng)格。這篇文章主要介紹了vue項(xiàng)目關(guān)閉eslint校驗(yàn),需要的朋友可以參考下2018-03-03
vue-router history模式服務(wù)器端配置過(guò)程記錄
vue路由有hash和history兩種模式,這篇文章主要給大家介紹了關(guān)于vue-router history模式服務(wù)器端配置的相關(guān)資料,需要的朋友可以參考下2021-06-06
關(guān)于net?6+vue?插件axios?后端接收參數(shù)問(wèn)題
接到這樣一個(gè)項(xiàng)目需求是這樣的,前端vue?必須對(duì)象傳遞后端也必須對(duì)象接收,接下來(lái)通過(guò)本文給大家介紹下net?6+vue?插件axios?后端接收參數(shù)的問(wèn)題,需要的朋友可以參考下2022-01-01
Vue的hover/click事件如何動(dòng)態(tài)改變顏色和背景色
這篇文章主要介紹了Vue的hover/click事件如何動(dòng)態(tài)改變顏色和背景色問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
vue+electron 自動(dòng)更新的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue+electron 自動(dòng)更新的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-02-02
詳解vue組件化開(kāi)發(fā)-vuex狀態(tài)管理庫(kù)
本篇文章主要介紹了詳解vue組件化開(kāi)發(fā)-vuex狀態(tài)管理庫(kù),具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04
基于vue實(shí)現(xiàn)網(wǎng)站前臺(tái)的權(quán)限管理(前后端分離實(shí)踐)
這篇文章主要介紹了基于vue實(shí)現(xiàn)網(wǎng)站前臺(tái)的權(quán)限管理(前后端分離實(shí)踐),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
vue中for循環(huán)更改數(shù)據(jù)的實(shí)例代碼(數(shù)據(jù)變化但頁(yè)面數(shù)據(jù)未變)
這篇文章主要介紹了vue中for循環(huán)更改數(shù)據(jù)的實(shí)例代碼(數(shù)據(jù)變化但頁(yè)面數(shù)據(jù)未變)的相關(guān)資料,需要的朋友可以參考下2017-09-09

