需求分析

画布中显示需要标注的图片,鼠标绘制矩形进行标注(矩形绘制在图片需要标注的位置,矩形中显示标注的内容文字)。最后可以拿到标注的内容位置信息、标注信息等并且回显所有标注内容。

效果展示

使用的技术

vue.js(vue2) + fabric.js

实现代码

<template><div id="fabricCanvas"><div id="pic-label"><div class="canvasDraw"><el-button @click="getData">保存修改</el-button><div class="context__x"><canvas ref="canvas" id="labelCanvas"> </canvas><!-- 编辑和删除弹窗 --><divid="menu"class="menu-x"v-show="showCon":style="menuPosition"@contextmenu.prevent=""ref="menu"><div><ul><li v-for="(item, index) in tagData" @click="changeTag(item)">{{ item.value }}</li></ul></div><div class="del" @click="delEl">删除</div></div></div></div><div class="tagCon"><div class="tagTitle" v-show="!isAdd"><div>标签栏</div><el-button type="primary" size="small" @click="addTag">添加标签</el-button></div><div class="tagDOM tagItem" v-show="isAdd"><el-inputv-model="tagCon"ref="addTask"@keyup.enter.native="addNewTag"></el-input><el-button type="text" size="small" @click="addNewTag">确定</el-button><el-button type="text" size="small" @click="cancelAdd">取消</el-button></div><!-- 搜索 --><div style="margin-top: 10px"><el-input placeholder="请输入"></el-input></div><ul style="margin-top: 15px"><li v-for="(item, index) in tagData" class="tagItem"><div v-show="!item.isEdit" class="tagDOM"><el-tooltipclass="item"effect="dark":content="item.value"placement="right-end"><span @click="changeTag(item)" class="tagName">{{ item.value }}</span></el-tooltip><div class="iconCon"><iclass="el-icon-edit editIcon"@click="changeEdit(item, index)"></i><i class="el-icon-delete delIcon" @click="delTag(item)"></i></div></div><div class="tagDOM" v-show="item.isEdit"><el-inputv-model="item.value"ref="editTask"@keyup.enter.native="changeText(item)"></el-input><el-button type="text" size="small" @click="changeText(item)">确定</el-button><el-button type="text" size="small" @click="cancelChange(item)">取消</el-button></div></li></ul></div></div></div>
</template><script>
import { fabric } from "fabric";
import { uuid } from "vue-uuid";
export default {name: "",data() {return {canvasInfo: {width: "",height: "",},editorCanvas: "",mouseFrom: {},mouseTo: {},showCon: false,drawingObject: null,currentTarget: null,menuPosition: null,rectId: "",activeEl: "",isDrawing: false,currentType: "rect",// 标签栏isAdd: false,tagCon: "",tagData: [{value: "电视",id: "1",isEdit: false,},// {//   value: "电视柜",//   id: "2",//   isEdit: false,// },// {//   value: "灯",//   id: "3",//   isEdit: false,// },],};},mounted() {// 后端返回:图片的长宽 2560 1200 ,用于等比例缩放图片this.canvasInfo.width = 2560 / 2;this.canvasInfo.height = 1200 / 2;this.init();// 监听键盘时间,按下backspace进行删除document.onkeydown = (e) => {let key = window.event.keyCode;// console.log("key", key);const isEdit = this.tagData.every((item) => item.isEdit == false);console.log("isEdit", isEdit);if (key == 8 && isEdit) {this.backSpaceDel();}};},methods: {// 按下backspace进行删除backSpaceDel() {// console.log("item", this.activeEl);// this.activeEl选中的标注内容if (this.activeEl) {this.editorCanvas.getObjects().forEach((item) => {console.log("item", item);if (item.rectId == this.activeEl.rectId) {this.editorCanvas.remove(item);}});this.editorCanvas.requestRenderAll();}},// 删除标签栏的tag---(实际项目中配合联调接口删除)delTag(item) {this.$confirm(`此操作将永久删除标签${item.value}, 是否继续?`, "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {item.isEdit = false;let text = item.value;this.editorCanvas.getObjects().forEach((item1) => {console.log("item111--delTag", item1.textID, item.id);if (item1.textID && item1.textID == item.id) {this.editorCanvas.remove(item1);this.editorCanvas.requestRenderAll();}});// 删除标签this.tagData = this.tagData.filter((el) => el.id != item.id);console.log("tagData", this.tagData);this.$message({type: "success",message: "删除成功!",});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},init() {this.initeditorCanvas();this.initD();},// 初始化模板编辑画布initeditorCanvas() {// 根据canvas绘制保存的内容const str = JSON.parse(localStorage.getItem("canvasdata"));console.log("str", str);// 初始化canvasthis.editorCanvas = new fabric.Canvas("labelCanvas", {// devicePixelRatio: true,width: this.canvasInfo.width, // canvas 宽height: this.canvasInfo.height,backgroundColor: "#ffffff",transparentCorners: false,fireRightClick: true, // 启用右键,button的数字为3stopContextMenu: true, // 禁止默认右键菜单});// this.editorCanvas.preserveObjectStacking = true;// this.editorCanvas.selectable = false;// this.editorCanvas.selection = false;// this.editorCanvas.toJSON(['rectId'])// this.editorCanvas.skipTargetFind = true;var img = "https://i1.mifile.cn/f/i/18/mitv4A/40/build.jpg";// mounted内预设的比例(由于图片太大,展示不下,实际项目中可以根据后端返回的图片大小范围去设置缩放比例)const scaleX = this.canvasInfo.width / 2560;const scaleY = this.canvasInfo.height / 1200;// 将图片设置成背景this.editorCanvas.setBackgroundImage(img,this.editorCanvas.renderAll.bind(this.editorCanvas), // 刷新画布{scaleX,scaleY,originX: "left",originY: "top",left: 0,top: 0,});/*** 模型返回的绘制:根据拿到的left、top, width, height去绘制新矩形(根据图片与canvas的比例)drawRect()*/// 监听鼠标右键的执行this.editorCanvas.on("mouse:down", this.canvasOnMouseDown);// 数据回显if (str) {// this.editorCanvas.loadFromJSON(str)this.editorCanvas.loadFromJSON(str,this.editorCanvas.renderAll.bind(this.editorCanvas),function (o, object) {// `o` = json object// `object` = fabric.Object instance// ... do some stuff ...// console.log('objqwe', o, object)});}},initD() {this.editorCanvas.on("mouse:down", (options) => {// 记录当前鼠标的起点坐标if (!this.editorCanvas.getActiveObject()) {this.mouseFrom.x = options.pointer.x;this.mouseFrom.y = options.pointer.y;this.isDrawing = true;}});// 监听鼠标移动this.editorCanvas.on("mouse:move", (options) => {// console.log("move", options);if (!this.editorCanvas.getActiveObject() && this.isDrawing) {console.log("move");this.mouseTo.x =options.pointer.x > this.editorCanvas.width? this.editorCanvas.width: options.pointer.x;this.mouseTo.y =options.pointer.y > this.editorCanvas.height? this.editorCanvas.height: options.pointer.y;}});this.editorCanvas.on("mouse:up", (options) => {this.isDrawing = false;// console.log("mouse:up", options);if (!this.editorCanvas.getActiveObject() &&this.currentType == "rect") {// 解决绘制的时候超出边界this.mouseTo.x =options.pointer.x > this.editorCanvas.width? this.editorCanvas.width: options.pointer.x;this.mouseTo.y =options.pointer.y > this.editorCanvas.height? this.editorCanvas.height: options.pointer.y;// 宽高为负值或为0let width = this.mouseTo.x - this.mouseFrom.x;let height = this.mouseTo.y - this.mouseFrom.y;// 如果点击和松开鼠标,都是在同一个坐标点或者反向,不绘制矩形if (width <= 0 || height <= 0) return;this.drawRect();}this.editorCanvas.renderAll();});this.editorCanvas.on("object:moving", (e) => {// 边界处理var obj = e.target;// if object is too big ignoreif (obj.currentHeight > obj.canvas.height ||obj.currentWidth > obj.canvas.width) {return;}obj.setCoords();// top-left  cornerif (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top);obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left);}// bot-right cornerif (obj.getBoundingRect().top + obj.getBoundingRect().height >obj.canvas.height ||obj.getBoundingRect().left + obj.getBoundingRect().width >obj.canvas.width) {obj.top = Math.min(obj.top,obj.canvas.height -obj.getBoundingRect().height +obj.top -obj.getBoundingRect().top);obj.left = Math.min(obj.left,obj.canvas.width -obj.getBoundingRect().width +obj.left -obj.getBoundingRect().left);}});this.editorCanvas.on("object:scaling", (options) => {// console.log("scale", options);var text = options.target.item(1);let group = options.target;console.log("text", text.width, group.width, group.getScaledWidth());let scaleX = group.width / group.getScaledWidth();let scaleY = group.height / group.getScaledHeight();text.set({fontSize: 14,scaleX,scaleY,});});},// 绘制矩形/*** 绘制的原理:矩形+文字的组合*/drawRect() {console.log("绘图啦11111", this.mouseFrom, this.mouseTo);// 通过UUID拿到唯一的IDlet rectId = uuid.v1();/*** 删除之前的this.drawingObject*/if (this.drawingObject) {this.editorCanvas.remove(this.drawingObject);}this.drawingObject = null;// 计算矩形长宽let left = this.mouseFrom.x;let top = this.mouseFrom.y;let width = this.mouseTo.x - this.mouseFrom.x;let height = this.mouseTo.y - this.mouseFrom.y;const drawingObject = new fabric.Rect({width: width,height: height,fill: "#d70202",lockRotation: true,opacity: 0.5,rectId,lockScalingFlip: true, // 禁止负值反转originX: "center",originY: "center",});const text = new fabric.Textbox("", {// width,// height,fontFamily: "Helvetica",fill: "white", // 设置字体颜色fontSize: 14,textAlign: "center",rectId,lockScalingX: true,lockScalingY: true,lockScalingFlip: true, // 禁止负值反转originX: "center",originY: "center",});if (drawingObject) {const group = new fabric.Group([drawingObject, text], {rectId,left: left,top: top,width: width,height: height,lockScalingFlip: true,lockRotation: true,});this.editorCanvas.add(group);console.log("this.editorCanvas", this.editorCanvas);this.editorCanvas.renderAll();this.drawingObject = drawingObject;// 绘制完成展示右键菜单栏(因为在鼠标绘制时,通过mouseup拿不到绘制的内容)let len = this.editorCanvas._objects.length;let curOptions = this.editorCanvas._objects[len - 1];this.showMenuCon(curOptions);}},// 绘制时展示右键菜单栏内容showMenuCon(options) {console.log(options);this.activeEl = options;// 当前鼠标位置let pointX = options.left + options.width * options.scaleX;let pointY = options.top;this.menuPosition = `left: ${pointX}px;top: ${pointY}px;`;this.showCon = true;},// 编辑changeEdit(item, index) {item.isEdit = true;// focus: 点击编辑聚焦this.$nextTick(() => this.$refs.editTask[index].focus());// console.log('this.$refs.editTask', this.$refs.editTask)},// 修改选中的标注内容changeText(item) {if (item.value == "") {this.$message({type: "error",message: "标签内容不能为空",offset: 200,});return;}item.isEdit = false;let text = item.value;this.editorCanvas.getObjects().forEach((item1) => {if (item1.textID && item1.textID == item.id) {console.log("item111", item1.textID, item.id);item1.item(1).set({text,originX: "center",originY: "center",textAlign: "center",});this.editorCanvas.requestRenderAll();}});},// 取消修改cancelChange(item) {item.isEdit = false;},// 右键菜单canvasOnMouseDown(options) {if (options.button === 3 && options.target && !options.target.rectId) {return;}this.activeEl = options.target;// console.log("opt", options);// 判断:右键,且在元素上右键// opt.button: 1-左键;2-中键;3-右键// 在画布上点击:opt.target 为 nullif (options.button === 3 && options.target) {// 获取当前元素// 设置右键菜单位置// 右键菜单的位置let pointX =options.target.left + options.target.width * options.target.scaleX;let pointY = options.target.top;// 设置右键菜单定位this.menuPosition = `left: ${pointX}px;top: ${pointY}px;`;this.showCon = true;} else {this.showCon = false;}},// 添加标签addTag() {this.isAdd = true;// input鼠标聚焦this.$nextTick(() => this.$refs.addTask.focus());},addNewTag() {if (this.tagCon.trim() == "") {this.message({type: "error",message: "内容不能为空",offset: 200,});return;}// 调接口this.tagData.push({value: this.tagCon,id: uuid.v1(),isEdit: false,});// 置空 关闭this.tagCon = "";this.isAdd = false;},// 取消添加cancelAdd() {this.isAdd = false;},// 根据选中的TAG进行修改changeTag(el) {if (this.activeEl) {// console.log("item", el.value, this.activeEl.rectId);let text = el.value;let textID = el.id;this.editorCanvas.getObjects().forEach((item) => {// console.log("item", item);if (item.rectId == this.activeEl.rectId) {// console.log("item", item);item.set({textID,});item.item(1).set({text,originX: "center",originY: "center",textAlign: "center",});}});this.editorCanvas.requestRenderAll();this.showCon = false;}},// 删除选中的元素delEl() {this.editorCanvas.getObjects().forEach((item) => {console.log("item", item);if (item.rectId == this.activeEl.rectId) {this.editorCanvas.remove(item);// console.log(item.rectId, this.activeEl.rectId)}});this.editorCanvas.requestRenderAll();this.showCon = false;},// 拿到canvas上的所有数据/*** 最终提交给后端要说明:scaleX,scaleY*/getData() {console.log("this.editorCanvas",this.editorCanvas,this.editorCanvas.toJSON());// rectId自定义属性localStorage.setItem("canvasdata",JSON.stringify(this.editorCanvas.toJSON(["rectId", "textID", "lockScalingFlip"])));console.log("getObjects", this.editorCanvas.getObjects());},},
};
</script><style lang="scss" scoped>
#fabricCanvas {padding: 20px;background: #f6f6f6;#pic-label {width: 100%;display: flex;justify-content: center;background: #f6f6f6;.canvasDraw {background: #fff;padding: 20px;}#labelCanvas {position: relative;box-shadow: 0 0 25px #cac6c6;width: 100%;display: block;// margin: 15px auto;height: 100%;#editDel {position: absolute;// top: 50%;// left: 50%;// transform: translate(-50%, -50%);width: 100px;height: 100px;line-height: 100px;background: red;color: white;text-align: center;margin: auto auto;z-index: 99999;}}.tagCon {width: 20%;margin-left: 20px;min-width: 250px;background: #fff;padding: 10px 20px;.tagTitle {height: 62px;font-size: 18px;font-weight: 700;display: flex;justify-content: space-between;align-items: center;// padding: 0 20px;box-sizing: border-box;}.tagItem {margin-bottom: 10px;border: 1px solid #dcdfe6;padding: 10px;border-left: 4px solid blue;}.tagDOM {display: flex;justify-content: space-between;align-items: center;.tagName {width: 70%;height: 40px;line-height: 40px;margin-right: 10px;display: inline-block;// width: 100px;text-align: left;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;font-size: 14px;color: #606266;}.iconCon {display: none;.editIcon {cursor: pointer;margin-right: 10px;}.delIcon {cursor: pointer;}}&:hover .iconCon {display: block;}}}}
}
</style>
<style scoped>
.context__x {position: relative;margin-top: 15px;
}.menu-x {width: 200px;position: absolute;background-color: #fff;border-radius: 4px;box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}.menu-x div {box-sizing: border-box;padding: 4px 8px;border-bottom: 1px solid #ccc;cursor: pointer;
}.menu-x ul > li:hover {background-color: antiquewhite;
}.menu-x .del:hover {background-color: antiquewhite;
}.menu-x div:first-child {border-top-left-radius: 4px;border-top-right-radius: 4px;
}.menu-x div:last-child {border-bottom: none;border-bottom-left-radius: 4px;border-bottom-right-radius: 4px;
}
.tagDOM >>> .el-input__inner {border: 0;padding: 0;
}
#fabricCanvas >>> .el-card__body,
.el-main {padding: 0;
}
</style>

学习使用Fabric

边界处理-拖拽

部分基本内容学习

Fabric.js 图形标注相关推荐

  1. Fabric.js组合图形

    Fabric.js组合图形 <!DOCTYPE html> <html lang="en"> <head><meta charset=&q ...

  2. Canvas实用库Fabric.js使用手册

    简介 什么是Fabric.js? Fabric.js是一个可以简化Canvas程序编写的库. Fabric.js为Canvas提供所缺少的对象模型, svg parser, 交互和一整套其他不可或缺的 ...

  3. 强大的Canvas开源库Fabric.js简介与开发指南

    什么是Fabric.js? Fabric.js 是一个强大且简单的Javascript HTML5 Canvas库. 官网地址:http://fabricjs.com/ 为什么要使用Fabric.js ...

  4. 【前端开发】Vue + Fabric.js + Element-plus 实现简易的H5可视化图片编辑器

    目录 前言 一 .实战效果 技术选型 核心功能 代码实现 二.Fabric.js 简介 安装 创建画布 监听画布事件 鼠标事件监听 设置画布背景 设置背景颜色 向画布添加图层对象 获取当前选中的对象 ...

  5. fabric.js学习

    一,前言 1.fabric [ˈfæbrɪk'] 是一个功能强大的运行在HTML5 canvas的JavaScript,fabric为canvas提供了一个交互式对象模型,通过简洁的api就可以在画布 ...

  6. 使用fabric.js简简单单实现一个画板

    什么是fabric fabric是一个功能强大的JavaScript库,运行在HTML5 canvas上.fabric为canvas提供了一个交互式对象模型,以及一个svg-to-canvas解析器. ...

  7. 我从 fabric.js 中学到了什么

    前言 熟悉 canvas 的朋友想必都使用或者听说过 Fabric.js,Fabric 算是一个元老级的 canvas 库了,从第一个版本发布到现在,已经有 8 年时间了.我近一年时间也在项目中使用, ...

  8. Fabric.js 使用图片遮盖画布(前景图)

    本文简介 点赞 + 关注 + 收藏 = 学会了 在 <Fabric.js 使用纯色遮挡画布> 中讲到使用纯色的方式遮盖画布.如果你的常见需要使用图片来遮盖的话,fabric.js 也提供了 ...

  9. vue 创建图片坐标点_利用vue+fabric.js获取图片坐标,并实现图片拖拽、旋转、拉伸等功能...

    什么是Fabric.js? Fabric.js是一个可以简化Canvas程序编写的库. Fabric.js为Canvas提供所缺少的对象模型, svg parser, 交互和一整套其他不可或缺的工具. ...

最新文章

  1. 有java基础的人学python_准备自学Python ,会java,有什么建议吗?
  2. LNMP之 nginx 启动脚本和配置文件
  3. JS一定要放在Body的最底部么?
  4. 重磅!居全国前列!合肥获批建设3个国家战略性新兴产业集群!
  5. Python爬虫应用实战案例-xpath正则表达式使用方法,爬取精美壁纸
  6. c语言函数 t啥意思,C语言函数大全(t开头)
  7. PAT(乙级) 1002 写出这个数 (20point(s)) Python
  8. 【一针见血】 JavaScript this
  9. python中可以使用变量来引用函数吗_如何在python语言中使用函数变量并调用函数...
  10. windows安装gnu_在Windows上安装GNU Emacs
  11. java 接口隔离_关于接口隔离原则的一个实现:
  12. mxm智能教育机器人无法智能对话_智能教育机器人与玩具的区别在哪里?
  13. JNDI:对java:comp/env的研究
  14. jdk api 1.8中文手册
  15. Math数学对象(万能随机数公式)
  16. DVD to MP4视频格式转换器v3.1.0 中文版
  17. 2019“智汇科学城”光明区创新企业投融资路演在招商局智慧城顺利举办
  18. 所有iOS设备的屏幕分辨率
  19. Json转对象失败:No suitable constructor found for type [simple type, class com.test.faster.domain.respons
  20. dns找不到服务器,“找不到服务器或DNS错误”,如何调整?

热门文章

  1. ADI官网资料检索技巧
  2. regsvr32命令
  3. Java监听器的处理方法_Java监听器的简单使用
  4. 这30个CSS选择器,你必须熟记(中)
  5. 卡巴斯基网络版客户端安装步骤
  6. Java 的设计模式
  7. ssm 一对多的映射关系
  8. antd组件DatePicker日期国际化错误 中英文都存在问题处理
  9. 基于微信小程序的快递取件及上门服务-计算机毕业设计
  10. 生信笔记:E值究竟是什么?!!!