前端页面对图片标注,绘制红色矩形框并在上边框添加文字,可以对一张图片进行多个标注,获取标注信息存到数据库。等下次加载把标注信息再复现到图片上,可以对标注的矩形框进行删除(二)
前端页面对图片标注,绘制红色矩形框并在上边框添加文字,可以对一张在图片上绘制红色矩形框并且可以在矩形框上添加文字说明,进行了功能的细化,逻辑上的处理,增加了一些功能,完善了一些,处理的一些存在的问题。在图片上进行红色矩形框的绘制并且在上边框添加文字,同时可以对一张图片进行多个标注,可以获取标注信息保存起来,存到数据库。3、鼠标按下收起绘制好了一个矩形,可以输入文字描述,点击添加可以对红色矩形框标注
接上一篇文章 图片标注功能,这次对vue项目中对图片标注功能,在图片上绘制红色矩形框并且可以在矩形框上添加文字说明,进行了功能的细化,逻辑上的处理,增加了一些功能,完善了一些,处理的一些存在的问题。下面对这个某块进行详细的功能说明。
在图片上进行红色矩形框的绘制并且在上边框添加文字,同时可以对一张图片进行多个标注,可以获取标注信息保存起来,存到数据库。等下次加载把标注信息再复现到图片上,可以对标注的矩形框进行删除。
页面布局如图所示:
1、非编辑模式下禁用清除、保存,文字添加功能;
2、编辑模式下,首先会获取对应图片的标注。有标注信息就绘制在图片上。
3、鼠标按下收起绘制好了一个矩形,可以输入文字描述,点击添加可以对红色矩形框标注添加文字说明。鼠标选中哪一个红色矩形框标注就可以给哪个红色矩形框添加说明。
4、鼠标点击图片上的红色矩形框标注,按键盘backspace可以删除该矩形框。
5、点击清除,会清除图片上临时的红色矩形框标注信息,然后点击保存才是真正对图片上的标注进行了清除;
6、点击退出编辑:会判断上次的标注信息和这次是否一样,一样就直接退出。不一样的话弹出提示,还未保存是否退出?确定的话就直接退出,新增加的红色矩形框标注会清理,保存之前的。取消的话不做任何操作。
7、编辑模式下,不允许切换照片。非编辑模式下,切换照片后,会绘制当前照片所保存的标注信息。
说明:该功能还存在一些瑕疵,等有空了继续修改。
代码如下:
这是html
<template>
<div id="markBox">
<div class="box-top">
<button class="btn-item" @click="toggleEditing">
{{ isEditing ? "退出编辑" : "开始绘制" }}
</button>
<button class="btn-item" @click="clearRectangles" :disabled="!isEditing">
清除
</button>
<button class="btn-item" @click="saveRectangles" :disabled="!isEditing">
保存
</button>
</div>
<div class="box-bottom">
<div class="box-bottom-top">
<data
class="img-item"
:class="{ 'img-item-active': curPic == item.id }"
v-for="(item, index) in srcList"
:key="index"
@click="cilckPic(item)"
>
<img :src="item.src" alt="" />
</data>
</div>
<div class="box-bottom-bottom">
<div class="inputbox">
<input
type="text"
v-model="description"
placeholder="输入描述文字"
:disabled="!isEditing"
@focus="isInputFocus = true"
@blur="isInputFocus = false"
/>
<button
class="add-btn"
@click="updateDescription"
:disabled="!isEditing"
>
添加
</button>
</div>
<canvas
ref="canvas"
:width="imageWidth"
:height="imageHeight"
@mousedown="onMouseDown"
@mousemove="drawRect"
@mouseup="endDrawing"
></canvas>
<img
:src="imageSrc"
alt="Image"
@load="initializeCanvas"
style="display: none"
/>
</div>
</div>
</div>
</template>
这是javascript
export default {
name: "mark",
data() {
return {
srcList: [
{ src: require("@/assets/images/1.jpg"), id: 1 },
{ src: require("@/assets/images/2.jpg"), id: 2 },
{ src: require("@/assets/images/3.jpg"), id: 3 },
{ src: require("@/assets/images/4.jpg"), id: 4 },
{ src: require("@/assets/images/5.jpg"), id: 5 },
],
curPic: 1,
imageSrc: require("@/assets/images/1.jpg"), // 确保图片路径正确
imageWidth: 600, // 根据你的图片宽度调整
imageHeight: 400, // 根据你的图片高度调整
isDrawing: false,
startX: 0,
startY: 0,
ctx: null,
img: new Image(),
rectangles: [], // 用于存储矩形框信息
selectedRectIndex: null, // 选中的矩形框索引
isEditing: false, // 编辑模式状态
description: "", //文字描述
isSaved: false, //是否保存了
isInputFocus: false, //输入框正在输入
};
},
mounted() {
this.$nextTick(() => {
this.loadRectangles(); // 加载保存的矩形框信息
});
},
methods: {
cilckPic(item) {
if (this.isEditing) {
this.$message("正在编辑不可切换");
return;
}
this.curPic = item.id;
this.imageSrc = item.src;
//切换图片时清除上一张的绘制效果
this.clearRectangles();
//获取本地是否已经绘制过当前图片
this.loadRectangles();
},
//图片加载获取canvas画板,绘制图片,同时绘制标注(如果存在)
initializeCanvas() {
const canvas = this.$refs.canvas;
this.ctx = canvas.getContext("2d");
this.img.src = this.imageSrc;
this.img.onload = () => {
this.ctx.drawImage(this.img, 0, 0, this.imageWidth, this.imageHeight);
this.redrawRectangles();
};
},
toggleEditing() {
//进入编辑状态,设置isSave为false
if (this.isEditing == false) {
this.isSaved = false;
this.isEditing = !this.isEditing;
} else {
//判断标注是否有变化:
// 1、无变化,直接退出编辑,然后提示无变化
// 2、有变化,提示是否保存;选择是,保存退出;选择清除绘制,加载已有的
const nowRectangles = JSON.stringify(this.rectangles);
const savedRectangles = localStorage.getItem(
"savedRectangles" + this.curPic
);
if (nowRectangles == savedRectangles) {
this.isEditing = !this.isEditing;
this.$message.warning("未作修改");
return;
}
if (this.isSaved) {
this.isEditing = !this.isEditing;
} else {
this.$confirm("还未保存是否退出?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
//图片上的全部清除,然后读取存在的
this.clearRectangles();
this.loadRectangles();
this.isEditing = !this.isEditing;
})
.catch(() => {
});
}
}
},
//鼠标放下
onMouseDown(event) {
if (!this.isEditing) return;
const x = event.offsetX;
const y = event.offsetY;
// 检查点击位置是否在某个矩形框内
this.selectedRectIndex = this.rectangles.findIndex((rect) => {
return (
x >= rect.x &&
x <= rect.x + rect.width &&
y >= rect.y &&
y <= rect.y + rect.height
);
});
if (this.selectedRectIndex === -1) {
// 如果没有选中矩形框,开始绘制新矩形框
this.isDrawing = true;
this.startX = x;
this.startY = y;
} else {
// 如果选中矩形框,监听键盘事件
window.addEventListener("keydown", this.onKeyDown);
}
},
//鼠标移动过程中
drawRect(event) {
if (!this.isDrawing) return;
const canvas = this.$refs.canvas;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.ctx.drawImage(this.img, 0, 0, this.imageWidth, this.imageHeight);
// 绘制已有的矩形框
this.redrawRectangles();
// 绘制当前矩形框
const currentX = event.offsetX;
const currentY = event.offsetY;
const width = currentX - this.startX;
const height = currentY - this.startY;
this.ctx.strokeStyle = "red";
this.ctx.setLineDash([5, 3]);
this.ctx.lineWidth = 2;
this.ctx.strokeRect(this.startX, this.startY, width, height);
},
endDrawing(event) {
if (!this.isDrawing) return;
this.isDrawing = false;
const currentX = event.offsetX;
const currentY = event.offsetY;
const width = currentX - this.startX;
const height = currentY - this.startY;
if (width == 0 || height == 0) {
//如果宽或高为0,不绘制
return;
}
// 保存当前矩形框信息
this.rectangles.push({
x: this.startX,
y: this.startY,
width: width,
height: height,
description: "", // 初始化描述文字
});
//此处表述,当某绘制完成的矩形
this.selectedRectIndex = this.rectangles.length - 1;
},
onKeyDown(event) {
if (this.isInputFocus) {
return;
}
if (event.key === "Backspace" && this.selectedRectIndex !== null) {
// 删除选中的矩形框
this.rectangles.splice(this.selectedRectIndex, 1);
this.selectedRectIndex = null;
// 重新绘制矩形框
this.ctx.clearRect(0, 0, this.imageWidth, this.imageHeight);
this.ctx.drawImage(this.img, 0, 0, this.imageWidth, this.imageHeight);
this.redrawRectangles();
// 取消键盘事件监听
window.removeEventListener("keydown", this.onKeyDown);
}
},
clearRectangles() {
this.rectangles = [];
this.ctx.clearRect(0, 0, this.imageWidth, this.imageHeight);
this.ctx.drawImage(this.img, 0, 0, this.imageWidth, this.imageHeight);
},
saveRectangles() {
localStorage.setItem(
"savedRectangles" + this.curPic,
JSON.stringify(this.rectangles)
);
this.isSaved = true;
this.$message.success("矩形框已保存到 localStorage");
},
loadRectangles() {
const canvas = this.$refs.canvas;
this.ctx = canvas.getContext("2d");
const savedRectangles = localStorage.getItem(
"savedRectangles" + this.curPic
);
if (savedRectangles) {
this.rectangles = JSON.parse(savedRectangles);
this.redrawRectangles();
}
},
updateDescription() {
if (!this.rectangles[0]) {
this.$message.warning("未绘制标注");
return;
}
if (this.selectedRectIndex == null || this.selectedRectIndex == -1) {
this.$message.warning("未选择标注框");
return;
}
if (!this.description.trim()) {
this.$message.warning("描述信息不可为空");
return;
}
this.rectangles[this.selectedRectIndex].description = this.description;
// 重新绘制矩形框,更新描述文字
this.ctx.clearRect(0, 0, this.imageWidth, this.imageHeight);
this.ctx.drawImage(this.img, 0, 0, this.imageWidth, this.imageHeight);
this.redrawRectangles();
},
redrawRectangles() {
this.rectangles.forEach((rect) => {
this.ctx.strokeStyle = "red";
this.ctx.setLineDash([5, 3]);
this.ctx.lineWidth = 2;
this.ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
// 绘制描述文字
if (rect.description) {
this.ctx.font = "16px Arial";
this.ctx.fillStyle = "red";
this.ctx.fillText(rect.description, rect.x, rect.y - 5);
}
});
this.description = "";
},
},
};
这是css样式
#markBox {
height: 100%;
width: 100%;
text-align: center;
padding-top: 20px;
.box-top {
display: flex;
align-items: center;
justify-content: center;
.btn-item {
height: 30px;
width: 100px;
margin: 5px;
}
}
.box-bottom {
.box-bottom-top {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
.img-item {
width: 100px;
height: 60px;
margin: 0px 5px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.img-item-active {
border: 1px solid #3fffff;
}
}
.box-bottom-bottom {
.inputbox {
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 10px;
input {
margin: 0;
width: 180px;
height: 30px;
box-sizing: border-box;
outline: none;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border: 1px solid #ccc;
padding-left: 5px;
}
.add-btn {
height: 30px;
width: 60px;
border: 1px solid #ccc;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-left: none;
}
}
}
input {
display: block;
margin: 10px auto;
}
canvas {
border: 1px solid black;
}
}
//公共
button[disabled] {
cursor: not-allowed;
}
input[disabled] {
cursor: not-allowed;
}
}
更多推荐
所有评论(0)