VUE:使用canvas绘制管线/管廊(二)

1,908 阅读3分钟

书接上回,在vue中,使用canvas来绘制管线,我来说一下我的实现思路。

首先,因canvas的绘制离不开JS,但是在vue页面中来书写,又会导致vue页面代码量过多,所以,我单独写了一个js文件,通过 export 进行导出,在vue页面中进行调用,下面来看代码:

index.vue

<template>
    <div class="canvas-container">
        <div class="canvas-icon-content">左侧选项列表</div>
        <div class="canvas-content">
            <div class="canvas" id="canvas">
                <canvas id="myCanvas" ref="myCanvas"></canvas>
            </div>
            <div class="canvas-options">下方操作按钮</div>
        </div>
    </div>
</template>


<script>

import canvas from '../utils/canvas'

let myCanvas = {}

export default {
    name: 'index',
    
    data() {
        return {}
    },
    
    mounted() {
    
        myCanvas = canvas.init('myCanvas')
        
    },
}
</script>

在 vue 页面中,主要是针对整体界面的搭建,css样式进行编写,其中除了界面外,还有 管线设备 信息修改的弹窗界面编写,如下图

image.png

image.png

针对信息编辑后的 “确定” 及 “取消” 事件,全部通过调用 myCanvas 中的方法来进行。

JS文件

在JS文件中,

首先定义一个 allElementCollection 数组,这个数组最终需要提交给后端,同时,页面中元素的绘制主要来自于这个主要数组。

剩下的就是来添加绘制的工作,以及JS中数据传入vue页面,vue页面的数据,传入JS中。

数据传输这里,我是这样做的,定义了一个对象 canvasDraw ,里面部分方法,如下代码:

const canvasDraw = {
    init(element) {
        canvas = document.getElementById(element)
        ctx = canvas.getContext('2d')

        const w = 1200, h = 800;
        canvas.width = w * devicePixelRatio;
        canvas.height = h * devicePixelRatio;
        canvas.style.width = w + 'px';
        canvas.style.height = h + 'px';

        return canvas
    },

    // 回传鼠标抬起事件
    canvasMouseUp: (e) => {},

    // 绘制类型切换
    drawTypeChange: (ele) => {},

    // 修改管线类型(冷热水)
    changePipelineType: (type) => {},

    // 设备参数修改
    canvasModifyInfo: (info) => {},
    
    // 显示设备可拖动的区域范围
    showEquipmentIconArea: ()  => {},
    
    commit: () => {
        // todo
        // 提交事件
    },

    // 清除整个画布
   clearAll: (info) => {},
    
    // 数据回显
    echoData: (data) => {}
}

export default canvasDraw

上述代码中,所有的 canvas 相关的方法,都通过对象 canvasDraw 导出,在vue页面,就可以通过 myCanvas 来进行调用了。

接下来,我们需要一个构造函数,这个函数的作用是,通过构造函数,可以 new 多个对象,每个对象里面有鼠标按下的起点坐标 startXstartY,鼠标抬起的重点坐标 endXendY,以及绘制的类型、绘制不同类型的信息对象、绘制形状的方法、绘制文字的方法、绘制图片的方法:

/**
 * 创建绘制元素工厂函数
 *
 * */
class ElementFactory {
    constructor(startX, startY, endX, endY) {
        this.startX = startX;  // 鼠标 按下 X点
        this.startY = startY;  // 鼠标 按下 Y点
        this.endX = endX;      // 鼠标 抬起 X点
        this.endY = endY;      // 鼠标 抬起 Y点
        
        this.type = 0;       // 绘制类型:图形、文字、图片

        this.pipelineInfo = {};  // 图形(管线)私有信息

        this.equipmentInfo = {};  // 图片(设备)私有信息

        this.textInfo = {};      // 文字(文字)私有信息
    }
    
    get minX() {
        return Math.min(this.startX, this.endX);
    }
    get maxX() {
        return Math.max(this.startX, this.endX);
    }
    get minY() {
        return Math.min(this.startY, this.endY);
    }
    get maxY() {
        return Math.max(this.startY, this.endY);
    }
    get middleX() {
        return this.endX - (this.endX - this.startX) / 2
    }
    get middleY() {
        return this.endY - (this.endY - this.startY) / 2
    }
    
    // 判断点击的是否存在元素绘制的范围之内
    isInside(x, y) {
        return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY
    }

    // 绘制管线
    drawPipeline() {}

    // 绘制设备
    drawEquipment() {}
    
    // 绘制设备上方文字
    drawEquipmentText() {}

    // 绘制纯文本
    drawText() {}
    
    // 根据条件来调用不同的绘制方法
    drawAllElement() {
        parseInt(this.type) === 0 ? this.drawPipeline() : (parseInt(this.type) === 1 ? this.drawEquipment() : this.drawText())
    }
}

基本的方法已经写完了,那接下来,就剩下一些鼠标的管理事件了。 在函数 canvasMousedown 中,主要处理三件事情:1、鼠标按下事件;2、鼠标移动事件;3、鼠标抬起事件。

鼠标按下的那一刻,有以下几个方面需要注意:

  1. 鼠标是左键点击还是右键点击;
  2. 当前鼠标点击的位置,即startXstartY
  3. 当前鼠标点击的位置是否是存在了已经绘制的内容;

具体代码如下:

const canvasMousedown = (e) => {
    const rect = canvas.getBoundingClientRect();
    const clickX = e.clientX - rect.left;
    const clickY = e.clientY - rect.top;
    
    // 查询所点击元素是否存在
    const shape = getElement(clickX, clickY);
    
    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))
        }
    }
};

其中 getElement 方法为:

// 鼠标点击canvas查看是否点击到了已经绘制的路线,若是,则返回相关线的对象,若否,返回null

const getElement = (x, y) => {
    for (let i = allElementCollection.length - 1; i >= 0; i--) {
        if (element.isInside(x, y)) return element;
    }
    return null
};

鼠标按下后,获取到 clickXclickY,判断当前点击的位置是否已经绘制了元素shape,如果shape存在,执行移动事件,如果不存在,则执行绘制事件。

大致的思路就是上述分享内容,接下来的文章中,我会将具体的方法及注意事项进行细化,如果您对这篇文章或者这个功能感兴趣的话,请点赞收藏吧,您的支持是我更新的动力~