vue3+canvas 绘制坐标

本文描述了解决Vue3中使用Canvas遇到的getContext错误问题,通过调整DOM操作时机和参考外部博主文章,提供了一段示例代码,涉及el-dialog组件和Canvas事件处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记录一下vue3+canvas遇到的bug:Cannot read properties of null reading ‘getContext‘

网上搜了好多没有解决,放在onMounted()、document.getElementById取节点等都不太行,最后参考这个博主的文章,完美解决(遇到此问题的可以参考哦,代码有点粗略请谅解)

<!-- 添加或修改监测点对话框 -->
<el-dialog :title="title" v-model="open" width="680px" append-to-body @close="destroyDialog">
     <el-form ref="elFormRef" :model="form" label-width="auto">
         <el-form-item label="小a" prop="aaa" style="width: 420px;">
             <el-input v-model="form.aaa" placeholder="请选择小a" disabled>
                 <template #append>
                     <el-button @click="openSel">选择</el-button>
                 </template>
             </el-input>
         </el-form-item>
         <el-form-item label="小b" prop="bbb" style="width: 600px;">
             <el-input v-model="form.xxx" placeholder="请选择小a,并绘制小b" disabled>
                 <template #append>
                     <el-button @click="clearAll">清除</el-button>
                 </template>
             </el-input>
         </el-form-item>
         <div class="picDiv" v-show="showImg">
             <canvas
                 @mousedown="mousedown($event)"
                 @mouseup="mouseup($event)"
                 @mousemove="mousemove($event)"
                 width="640" height="360" ref="myCanvas">
                 当前浏览器版本不支持,请升级
             </canvas>
         </div>
     </el-form>
     <template #footer>
         <div class="dialog-footer">
             <el-button type="primary" @click="submitForm">确 定</el-button>
             <el-button type="primary" @click="cancel">取 消</el-button>
         </div>
     </template>
 </el-dialog>
// dialog取消按钮
function cancel() {
    open.value = false;
    closeState()
}
// dialog提交按钮
function submitForm() {
   closeState()
}
/**
 * dialog 点击右上角 x 号关闭
 */
function destroyDialog() {
    closeState()
}

/** 点击修改按钮操作打开dialog */
function handleUpdate(row) {
    get(id).then(response => {
        form.value = response.data;
        open.value = true;
        showImg.value = true
        // 回显绘制图形,两种情况(注明:这种写法不可以,在dialog上面使用注销属性,若使用注销属性,①处的watch监听到注销时,会再次执行,let context = myCanvas.value.getContext("2d");这时的myCanvas为null,getContext会报错)
        if(ctx.value!=null){//如果canvas实例化了,就直接回显绘制图形(新增后进行修改操作,这时候canvas已经实例化了,ctx有值,因为watch监听到变化了)
            graphingEcho()
        }else {//进入监测点模块没有操作新增直接操作修改时,这时候需要操作canvas实例,但是canvas还没有实例化,就需要wacth一下,当实例化了就会执行graphingEcho方法,wacth不会阻塞其他操作(外面①处的watch还未来得及监听变化,此处执行较快,所以再次watch)
            watch(ctx, (newValue, oldValue) => {
                graphingEcho()
            })
        }
    })
}


// Todo canvas绘制点位
/** @type {HTMLCanvasElement} */
const myCanvas = ref(null)
const ctx = ref(null);
const staus = ref(false);
const zuobiaoList = ref([]);
const showImg = ref(false);
const open = ref(false);

//因为script 的速度 比标签要快 所以要监听这个myCanvas 不然获取不到   ①
watch(myCanvas, (newValue, oldValue) => {
    let context = myCanvas.value.getContext("2d");
    ctx.value = context;
})

/**
 * 绘制图形
 */
function graphing() {
    ctx.value.clearRect(0, 0, myCanvas.value.width, myCanvas.value.height); // 清除画布上的内容
    ctx.value.beginPath();
    ctx.value.moveTo(zuobiaoList.value[0].x, zuobiaoList.value[0].y); // 从第一个顶点开始绘制路径
    if (zuobiaoList.value.length > 3 && pointsDifference(zuobiaoList.value[0], zuobiaoList.value[zuobiaoList.value.length - 1])) {
        for (let i = 1; i < zuobiaoList.value.length; i++) {
            let point = zuobiaoList.value[i];
            if (i === zuobiaoList.value.length - 1) {
                ctx.value.lineTo(zuobiaoList.value[0].x, zuobiaoList.value[0].y)
                staus.value = false;//关闭绘制状态
            } else {
                ctx.value.lineTo(point.x, point.y); // 添加一条线到下一个顶点
            }
        }
        ctx.value.strokeStyle = "red" //设颜色
        ctx.value.lineWidth = 2;//线条粗细
        ctx.value.stroke(); // 描边,使多边形可见
        //因为最后一个点和第一个点坐标合并,所以使用第一个点
        zuobiaoList.value.pop();

        //转换格式存储
        const zuobiaoArray = zuobiaoList.value.map(item => [item.x, item.y]);
        form.value.quyuZuoBiao = JSON.stringify(zuobiaoArray);//格式转换赋值表单

        ElMessage({
            message: '绘制成功',
            type: 'success',
        })
    } else {
        for (let i = 1; i < zuobiaoList.value.length; i++) {
            let point = zuobiaoList.value[i];
            ctx.value.lineTo(point.x, point.y); // 添加一条线到下一个顶点
        }
        ctx.value.strokeStyle = "red" //设颜色
        ctx.value.lineWidth = 2;//线条粗细
        ctx.value.stroke(); // 描边,使多边形可见
    }
}

/**
 * 点击修改时 数据回显 绘制区域
 */
function graphingEcho() {
    //解析json坐标
    const formattedArray = JSON.parse(form.value.quyuZuoBiao);
    zuobiaoList.value = formattedArray.map(item => ({ x: item[0], y: item[1] }));
    //将第一个点添加至列表末尾,使图形闭合
    zuobiaoList.value.push(zuobiaoList.value[0]);

    ctx.value.clearRect(0, 0, myCanvas.value.width, myCanvas.value.height); // 清除画布上的内容
    ctx.value.beginPath();
    ctx.value.moveTo(zuobiaoList.value[0].x, zuobiaoList.value[0].y); // 从第一个顶点开始绘制路径
    for (let i = 1; i < zuobiaoList.value.length; i++) {
        let point = zuobiaoList.value[i];
        ctx.value.lineTo(point.x, point.y); // 添加一条线到下一个顶点
    }
    ctx.value.strokeStyle = "red" //设颜色
    ctx.value.lineWidth = 2;//线条粗细
    ctx.value.stroke(); // 描边,使多边形可见
}

/**
 * 鼠标落下
 */
function mousedown(event) {
    if (!staus.value) return;
    //对于画线而言这是 鼠标按下时候获取
    zuobiaoList.value.push({ x: event.offsetX, y: event.offsetY });
    if (zuobiaoList.value.length > 1) {
        graphing()
    }
}

/**
 * 获取鼠标在画布移动的坐标
 */
function mousemove(XY) {
    if (!staus.value) return;
    //用于实时绘图
    if (zuobiaoList.value.length > 0) {
        graphing()
        ctx.value.beginPath();
        ctx.value.moveTo(zuobiaoList.value[zuobiaoList.value.length - 1].x, zuobiaoList.value[zuobiaoList.value.length - 1].y); //第一个点
        ctx.value.lineTo(XY.offsetX, XY.offsetY); //第二个点
        ctx.value.strokeStyle = "red" //设颜色
        ctx.value.lineWidth = 2;//线条粗细
        ctx.value.stroke(); //开始绘制(把前面点连接)
    }
}

/**
 * 鼠标左键弹起时候
 */
function mouseup(XY) {
    // console.log("鼠标弹起")
}

/**
 * 计算两个点之间的距离是否在5以内 用于判定两个点是否重合
 * @param {Object} point1
 * @param {Object} point2
 */
function pointsDifference(point1, point2) {
    let diffX = Math.abs(point1.x - point2.x);
    let diffY = Math.abs(point1.y - point2.y);
    return Math.sqrt(diffX * diffX + diffY * diffY) <= 5;
}

/**
 * 清除绘制点位
 */
function clearAll() {
    ctx.value.clearRect(0, 0, myCanvas.value.width, myCanvas.value.height);
    zuobiaoList.value = []
    // 当清除绘制点位后,重新开启绘制状态
    if(showImg){//开启条件为图片已经存在
        staus.value = true;
    }
    form.value.quyuZuoBiao = null;//清除坐标值
}

/**
 * 开启截屏图片显示状态、开启绘制状态
 */
function openState(){
    showImg.value = true;
    staus.value = true;
}

/**
 * 关闭截屏图片显示状态、关闭绘制状态
 */
function closeState(){
    showImg.value = false;
    staus.value = false;
    zuobiaoList.value = []
    ctx.value.clearRect(0, 0, myCanvas.value.width, myCanvas.value.height);
}
<style scoped lang="scss">
.picDiv{
    width: 640px;
    height: 360px;
    background-image: url(https://ts1.cn.mm.bing.net/th/id/R-C.67cc9fd8d48489fbd7a4d750ea1fb10f?rik=EyzK18HBPX16zw&riu=http%3a%2f%2fwww.2008php.com%2f2014_Website_appreciate%2f2014-09-04%2f20140904210838.jpg&ehk=dQL%2bdfOul%2bIaLtJhMTEUJz1ikYoAmgcfx9BpaFgcLfU%3d&risl=&pid=ImgRaw&r=0);
    background-repeat: no-repeat;
    background-size: cover;
}
</style>
实现拓扑图可以使用 Vue3Canvas 结合来完成。下面是一个简单的实现过程: 1. 首先,需要安装 Vue3Canvas 库: ``` bash npm install vue@next npm install canvas --save ``` 2. 在 Vue3 中创建一个组件,用于渲染拓扑图。在组件中引入 Canvas 库: ``` javascript <template> <canvas ref="canvas"></canvas> </template> <script> import { onMounted, ref } from &#39;vue&#39; import Canvas from &#39;canvas&#39; export default { setup() { const canvasRef = ref(null) onMounted(() => { const canvas = canvasRef.value const ctx = canvas.getContext(&#39;2d&#39;) // 在这里进行绘制 }) return { canvasRef } } } </script> ``` 3. 在组件的 `onMounted` 钩子函数中,获取 Canvas 的上下文对象 `ctx`,并进行绘制。可以使用 Canvas 的 API 画出线条、圆形等形状,也可以使用外部库来绘制更复杂的图形。 4. 在绘制时,可以将节点和线条信息存储在数组中,以方便后续的更新和交互。例如: ``` javascript // 存储节点和线条信息的数组 const nodes = [ {x: 100, y: 100, r: 20, color: &#39;#ff0000&#39;}, {x: 200, y: 200, r: 30, color: &#39;#00ff00&#39;} ] const links = [ {source: 0, target: 1}, {source: 1, target: 2} ] // 绘制节点 nodes.forEach(node => { ctx.beginPath() ctx.arc(node.x, node.y, node.r, 0, Math.PI * 2) ctx.fillStyle = node.color ctx.fill() }) // 绘制线条 links.forEach(link => { const source = nodes[link.source] const target = nodes[link.target] ctx.beginPath() ctx.moveTo(source.x, source.y) ctx.lineTo(target.x, target.y) ctx.stroke() }) ``` 以上就是一个简单的 Vue3Canvas 实现拓扑图的过程。需要注意的是,Vue3 的模板中不能直接使用 Canvas,需要通过 `ref` 引用实现。另外,绘制时需要注意节点和线条的位置信息,以及 Canvas坐标系。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值