记录一下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>