功能描述
- 待图片上传并加载完成后,重新生成画布;
- 鼠标在画布区域内绘制,移动,拖拽,删除矩形(如截图工具一般);
isboundary()
判断是否需要判断边界问题,默认false。
效果截图

实现代码
- vue页面
– 注意:原本canvas不支持键盘事件,为canvas加上tabindex=0,转为HTML5 DOM元素,方可支持键盘事件
<div class="phone-content">
<img class="phone-bg-image" ref="phoneImg"
v-show="form.bg_url"
:src="form.bg_url"/>
<canvas ref="markCanvas" tabindex="0"></canvas>
</div>
<script>
export default {
watch: {
"form.bg_url": {
handler(val) {
if (val) {
this.$nextTick(() => {
this.imgLoad();
});
} else {
this.$nextTick(() => {
this.redraw();
});
}
},
deep: true,
immediate: true,
},
},
methods: {
imgLoad() {
const imgNode = this.$refs.phoneImg;
var timer = setInterval(() => {
if (imgNode.complete) {
this.$nextTick(() => {
this.initCanvas();
});
clearInterval(timer);
}
}, 150);
},
},
initCanvas() {
let imgElement = this.$refs.phoneImg;
if (!imgElement) return this.$message.error("请先上传图片!");
let cav = this.cav = this.$refs.markCanvas;
cav.width = imgElement.offsetWidth;
cav.height = imgElement.offsetHeight;
let ctx = cav.getContext("2d");
ctx.strokeStyle = "blue";
cav.style.cursor = "crosshair";
this.markList = this.markList.map((value) => {
value.x = value.x * cav.width;
value.y = value.y * cav.height;
value.w = value.w * cav.width;
value.h = value.h * cav.height;
return value
});
if (this.markList.length !== 0) {
this.markList.forEach(function (value) {
ctx.rect(value.x, value.y, value.w, value.h);
ctx.stroke();
});
}
draw(cav, this.markList, null, true);
},
redraw() {
if (this.cav) {
let ctx = this.cav.getContext('2d');
let w = this.cav.width;
let h = this.cav.height;
ctx.clearRect(0, 0, w, h);
this.markList = []
this.cav.width = 0;
this.cav.height = 0;
ctx.closePath();
}
},
}
</script>
- draw.js
export let selectedGraphicsIndex = null;
let isCtxCanvasScope = false;
export function draw(cav, list, i, isCtxScope = false) {
let ctx = cav.getContext('2d');
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
isCtxCanvasScope = isCtxScope
console.log("是否在画布内作画:", { isCtxCanvasScope });
let sX = 0;
let sY = 0;
cav.onmousemove = function (em) {
sX = em.offsetX;
sY = em.offsetY;
let iem = undefined;
if (list.length === 0) {
newDraw(cav, ctx, list);
selectedGraphicsIndex = null;
} else if (i === undefined) {
selectedGraphicsIndex = null;
list.forEach(function (value, index) {
if (value.w > 0 && value.h > 0 && sX > value.x && sX < value.x + value.w && sY > value.y && sY < value.y + value.h) {
iem = index;
judgeDraw(cav, ctx, list, iem);
}
if (value.w < 0 && value.h > 0 && sX < value.x && sX > value.x + value.w && sY > value.y && sY < value.y + value.h) {
iem = index;
judgeDraw(cav, ctx, list, iem);
}
if (value.w > 0 && value.h < 0 && sX > value.x && sX < value.x + value.w && sY < value.y && sY > value.y + value.h) {
iem = index;
judgeDraw(cav, ctx, list, iem);
}
if (value.w < 0 && value.h < 0 && sX < value.x && sX > value.x + value.w && sY < value.y && sY > value.y + value.h) {
iem = index;
judgeDraw(cav, ctx, list, iem);
}
if (iem === undefined) {
newDraw(cav, ctx, list);
}
})
} else {
for (let index = 0; index < list.length; index++) {
let value = list[index];
if (sX < value.x + 5 && sX > value.x - 5 && sY < value.y + 5 && sY > value.y - 5) {
if (index === i) {
changeDraw(cav, ctx, list, i, 1);
break;
}
} else if (sX < value.x + value.w + 5 && sX > value.x + value.w - 5 && sY < value.y + 5 && sY > value.y - 5) {
if (index === i) {
changeDraw(cav, ctx, list, i, 2);
break;
}
} else if (sX < value.x + 5 && sX > value.x - 5 && sY < value.y + value.h + 5 && sY > value.y + value.h - 5) {
if (index === i) {
changeDraw(cav, ctx, list, i, 3);
break;
}
} else if (sX < value.x + value.w + 5 && sX > value.x + value.w - 5 && sY < value.y + value.h + 5 && sY > value.y + value.h - 5) {
if (index === i) {
changeDraw(cav, ctx, list, i, 4);
break;
}
} else if (value.w > 0 && value.h > 0 && sX > value.x && sX < value.x + value.w && sY > value.y && sY < value.y + value.h) {
iem = index
judgeDraw(cav, ctx, list, index);
break;
} else if (value.w < 0 && value.h > 0 && sX < value.x && sX > value.x + value.w && sY > value.y && sY < value.y + value.h) {
iem = index
judgeDraw(cav, ctx, list, index);
break;
} else if (value.w > 0 && value.h < 0 && sX > value.x && sX < value.x + value.w && sY < value.y && sY > value.y + value.h) {
iem = index
judgeDraw(cav, ctx, list, index);
break;
} else if (value.w < 0 && value.h < 0 && sX < value.x && sX > value.x + value.w && sY < value.y && sY > value.y + value.h) {
iem = index
judgeDraw(cav, ctx, list, index);
break;
} else {
if (iem === undefined) {
newDraw(cav, ctx, list);
selectedGraphicsIndex = null;
}
}
}
}
cav.onmouseout = function () {
if (i !== undefined) {
draw(cav, list, i, isCtxCanvasScope);
}
};
}
}
function changeDraw(cav, ctx, list, i, site) {
cav.style.cursor = 'pointer'
let mark = list[i];
cav.onmousedown = function (ed) {
let sX = ed.offsetX;
let sY = ed.offsetY;
let W = mark.w;
let H = mark.h;
let X = mark.x;
let Y = mark.y;
cav.onmousemove = function (em) {
let iframe = {}
switch (site) {
case 1:
iframe = {
x: em.offsetX,
y: em.offsetY,
w: W + (sX - em.offsetX),
h: H + (sY - em.offsetY)
}
if (isCtxCanvasScope) {
let w = W + sX - em.offsetX;
let h = H + sY - em.offsetY;
let obj = isboundary(cav, em.offsetX, em.offsetY, w, h);
iframe = {
x: obj.x,
y: obj.y,
w: obj.w,
h: obj.h
}
}
break;
case 2:
iframe = {
x: X,
y: em.offsetY,
w: W - sX + em.offsetX,
h: H + sY - em.offsetY
}
if (isCtxCanvasScope) {
let w = W - sX + em.offsetX;
let h = H + sY - em.offsetY;
let obj = isboundary(cav, X, em.offsetY, w, h);
iframe = {
x: obj.x,
y: obj.y,
w: obj.w,
h: obj.h
}
}
break;
case 3:
iframe = {
x: em.offsetX,
y: em.offsetY - (H - sY + em.offsetY),
w: W + sX - em.offsetX,
h: H - sY + em.offsetY
}
if (isCtxCanvasScope) {
let w = W + sX - em.offsetX;
let h = H - sY + em.offsetY;
let obj = isboundary(cav, em.offsetX, em.offsetY - h, w, h);
iframe = {
x: obj.x,
y: obj.y,
w: obj.w,
h: obj.h
}
}
break;
case 4:
iframe = {
x: X,
y: Y,
w: W - sX + em.offsetX,
h: H - sY + em.offsetY
}
if (isCtxCanvasScope) {
let w = W - sX + em.offsetX;
let h = H - sY + em.offsetY;
let obj = isboundary(cav, X, Y, w, h);
iframe = {
x: obj.x,
y: obj.y,
w: obj.w,
h: obj.h
}
}
break;
}
for (let key in iframe) {
list[i][key] = iframe[key];
}
reDraw(cav, ctx, list, i);
}
cav.onmouseout = function () {
reDraw(cav, ctx, list);
draw(cav, list, null, isCtxCanvasScope)
};
delDraw(cav, ctx, list, i);
}
}
function newDraw(cav, ctx, list) {
cav.style.cursor = 'crosshair'
let start = false;
let sX = 0;
let sY = 0;
cav.onmousedown = function (ed) {
start = true;
sX = ed.offsetX;
sY = ed.offsetY;
delDraw(cav, ctx, list, null)
cav.onmousemove = function (em) {
if (start) {
reDraw(cav, ctx, list);
ctx.beginPath();
ctx.setLineDash([8, 4]);
ctx.rect(sX, sY, em.offsetX - sX, em.offsetY - sY);
ctx.stroke();
}
}
cav.onmouseup = function (eu) {
if (start && Math.abs(eu.offsetX - sX) > 10 && Math.abs(eu.offsetY - sY) > 10) {
let frame = {
x: sX, y: sY, w: eu.offsetX - sX, h: eu.offsetY - sY
};
if (isCtxCanvasScope) {
let obj = isboundary(cav, sX, sY, eu.offsetX - sX, eu.offsetY - sY);
frame = {
x: obj.x, y: obj.y, w: obj.w, h: obj.h
};
}
list.push(frame);
reDraw(cav, ctx, list);
start = false
draw(cav, list, null, isCtxCanvasScope)
} else {
reDraw(cav, ctx, list);
start = false
draw(cav, list, null, isCtxCanvasScope)
}
};
cav.onmouseout = function (eo) {
if (start && Math.abs(eo.offsetX - sX) > 10 && Math.abs(eo.offsetY - sY) > 10) {
let frame = {
x: sX, y: sY, w: eo.offsetX - sX, h: eo.offsetY - sY
};
if (isCtxCanvasScope) {
let obj = isboundary(cav, sX, sY, eo.offsetX - sX, eo.offsetY - sY);
frame = {
x: obj.x, y: obj.y, w: obj.w, h: obj.h
};
}
list.push(frame);
reDraw(cav, ctx, list);
start = false;
draw(cav, list, null, isCtxCanvasScope)
} else {
reDraw(cav, ctx, list);
start = false
draw(cav, list, null, isCtxCanvasScope)
}
};
}
}
function judgeDraw(cav, ctx, list, iem) {
cav.style.cursor = 'default'
let sX = 0;
let sY = 0;
cav.onmousedown = function (ed) {
sX = ed.offsetX;
sY = ed.offsetY;
reDraw(cav, ctx, list, iem);
cav.onmouseup = function () {
reDraw(cav, ctx, list, iem);
draw(cav, list, iem, isCtxCanvasScope);
};
moveDraw(cav, ctx, list, iem, sX, sY);
delDraw(cav, ctx, list, iem);
}
selectedGraphicsIndex = iem;
}
function moveDraw(cav, ctx, list, i, sX, sY) {
let mark = list[i]
let X = mark.x
let Y = mark.y
cav.onmousemove = function (em) {
let iframe = {
x: X + em.offsetX - sX,
y: Y + em.offsetY - sY,
w: mark.w,
h: mark.h
}
if (isCtxCanvasScope) {
let x = X + em.offsetX - sX;
let y = Y + em.offsetY - sY;
let obj = isboundary(cav, x, y, mark.w, mark.h);
iframe = {
x: obj.x,
y: obj.y,
w: obj.w,
h: obj.h
}
}
for (let key in iframe) {
list[i][key] = iframe[key];
}
delDraw(cav, ctx, list, i);
reDraw(cav, ctx, list, i);
}
cav.onmouseup = function () {
reDraw(cav, ctx, list, i);
draw(cav, list, i, isCtxCanvasScope);
};
}
function delDraw(cav, ctx, list, i) {
if (i === null) {
cav.onkeydown = function () {
return false;
}
} else {
cav.onkeydown = function (k) {
let key = k.keyCode || k.which;
if (key == 8 && i !== null) {
if (list.length >= 1) {
list.splice(i, 1);
reDraw(cav, ctx, list);
} else {
ctx.clearRect(0, 0, cav.width, cav.height);
}
delDraw(cav, ctx, list, null)
reDraw(cav, ctx, list);
draw(cav, list, null, isCtxCanvasScope);
}
}
}
}
function reDraw(cav, ctx, list, i) {
ctx.setLineDash([8, 0]);
ctx.clearRect(0, 0, cav.width, cav.height);
list.forEach(function (value, index) {
if (i === undefined || index != i) {
ctx.beginPath();
ctx.strokeStyle = 'blue';
ctx.rect(value.x, value.y, value.w, value.h);
ctx.stroke();
}
});
list.forEach(function (value, index) {
if (index === i) {
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.rect(value.x, value.y, value.w, value.h);
ctx.fillStyle = 'RGBA(102,102,102,0.2)'
ctx.fillRect(value.x, value.y, value.w, value.h);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.arc(value.x, value.y, 4, 0, Math.PI * 2)
ctx.fillStyle = "white";
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(value.x, value.y + value.h, 4, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(value.x + value.w, value.y + value.h, 4, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(value.x + value.w, value.y, 4, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
ctx.stroke();
}
})
}
function isboundary(cav, x, y, markW, markH) {
let width = cav.width;
let height = cav.height;
if (x < 0) {
x = 0
} else if ((x + markW) > width) {
x = width - markW - 4 > 0 ? width - markW - 4 : 0
}
if (y < 0) {
y = 0
} else if ((y + markH) > height) {
y = height - markH - 4 > 0 ? height - markH - 4 : 0
}
return { x, y, w: markW, h: markH }
}
参考地址
适用于vue等架构的的原生js实现画布中鼠标移动绘制、编辑矩形