需求在一张图片上标注矩形和多边形,支持回显;
fabric版本:4.6.0;
Fabric.js 是一个功能强大且操作简单的 Javascript HTML5 canvas 工具库。
官方文档
参考链接
组件代码drawer.vue
createUuid 是为了让每一个图形有自己的id;方便用于获取用户点击的那个图形等操作;
defaultRectStyle、defaultPolygonStyle是自己定义的一些默认的样式,比如颜色,边框大小等;
<template>
<div
class="drawer"
:style="`width: ${width}px; height: ${height}px`"
>
<div
v-show="editable"
class="canvans"
>
<canvas ref="canvas" />
</div>
<div class="container">
<slot />
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import { fabric } from 'fabric';
import { createUuid } from '@/utils/uuid';
export const enum DrawType {
Pointer = '',
Polygon = 'polygon',
Rect = 'rectangle',
Line = 'Line',
}
const defaultRectStyle: OthersConfigModel = {
stroke: 'rgb(0, 232, 8)',
strokeWidth: 1,
fill: '',
opacity: 0.8,
cornerColor: 'rgb(0, 232, 8)',
cornerSize: 4,
selectionLineWidth: 0,
hasBorders: false
};
const defaultPolygonStyle: OthersConfigModel = {
stroke: 'rgb(255, 30, 0)',
strokeWidth: 1,
fill: '',
opacity: 0.8,
cornerColor: 'rgb(255, 30, 0)',
cornerSize: 4,
cornerStyle: 'circle',
selectionLineWidth: 0
};
const defaultCircleStyle: OthersConfigModel = {
fill: '#FFE700'
// selectionLineWidth: 0
};
export interface MousePosition {
x: number;
y: number;
}
export interface Shape {
type: DrawType | string;
points: Array<MousePosition | number> | number[][];
content?: number|string;
others?: OthersConfigModel;
}
export interface RectModel {
left: number;
top: number;
width: number;
height: number;
scaleX?: number;
scaleY?: number;
}
export interface OthersModel{
stroke: string;
opacity: number;
uuid: string | null;
strokeWidth: number;
fill: string;
cornerColor: string;
cornerStyle: string;
cornerSize: number;
radius: number;
selectionLineWidth: number;
clickIndex: number;
hasBorders: boolean;
hasControls: boolean; // 不显示边框点
selectable: boolean; // 禁止选中当前元素
lockMovementX: boolean; // 禁止元素移动
lockMovementY: boolean;
lockScalingX: boolean; // 禁止元素缩
lockScalingY: boolean;
standard_item_id: number;
visible: boolean; // 设置元素不可见
}
export type OthersConfigModel = Partial<OthersModel>
@Component()
export default class Drawer extends Vue {
@Prop({
type: Number,
required: false,
default: 780
})
private width!: number;
@Prop({
type: Number,
required: false,
default: 580
})
private height!: number;
@Prop({ type: Boolean, default: true })
private editable!: boolean;
// @PropSync('shapes', { type: Array })
// private bindShapes!: Array<Shape>;
@Prop({
type: Object,
required: false,
default: () => {}
})
private rectStyle!: {};
@Prop({
type: Object,
required: false,
default: () => {}
})
private polygonStyle!: {};
private canvas: any = null;
private drawType = '';
private drawingObject: any = null;
private drawingShape: any[] = [];
private operateAttribute = ['lockMovementX', 'lockMovementY', 'lockScalingX', 'lockScalingY']; // 画框的时候不能放大缩小移动;
private canvasObjects: number[] | null = null; // 用户点击置于顶层/底层 导致顺序变化 ids变化 index也变化
// 改变时候触发
private async onChange () {
// 防止用户将不想要的也传出去
this.$emit('onChange', this.canvas.getObjects().filter(x => x.uuid));
}
// 将点位比例缩小 从后台传来渲染
public narrowPolygon (narrowPolygon: MousePosition[], others?: OthersConfigModel) {
const polygon = this.drawPolygon(narrowPolygon, others);
polygon.centerPoints = polygon.getCenterPoint(); // 获取中心点
this.canvas.add(polygon);
}
public narrowRect (points: number[], others?: OthersConfigModel) {
this.canvas.add(this.drawRect(points, others));
}
public narrowCircle (narrowCircle: MousePosition, others?: OthersConfigModel) {
this.canvas.add(this.drawCircle(narrowCircle, others));
}
// 展示组 矩形+文字
public showRectGroup (points: number[], text: string, others?: OthersConfigModel) {
const rect = this.drawRect(points, others);
const content = this.showContent(rect, text);
const group = this.getGroup(rect, content);
this.canvas.add(group);
}
// 展示组 多边形+文字
public showPolygonGroup (narrowPolygon: MousePosition[], text: string, others?: OthersConfigModel) {
const polygon = this.drawPolygon(narrowPolygon, others);
polygon.centerPoints = polygon.getCenterPoint(); // 获取中心点
const content = this.showContent(polygon, text);
const group = this.getGroup(polygon, content);
this.canvas.add(group);
}
// 删除选中
public remove (object: any) {
this.canvas.remove(object);
}
// 删除选中
public removeItemByIndex (itemIndex: number) {
(this.canvasObjects ?? this.canvas.getObjects()).forEach((item, index) => {
if (!item.uuid) {
itemIndex += 1;
return;
}
if (index === itemIndex) {
this.remove(item);
}
});
const spliceObject = this.canvasObjects?.splice(itemIndex, 1);
}
public removeAll () {
this.canvas.getObjects().forEach(item => {
this.canvas.remove(item);
});
}
// 设置为当前活跃
public async setActiveObjectByIndex (itemIndex: number) {
(this.canvasObjects ?? this.canvas.getObjects()).forEach((item, index) => {
if (!item.uuid) {
itemIndex += 1;
return;
}
if (index === itemIndex) {
this.canvas.setActiveObject(item);
}
});
}
public setVisible (visible: boolean) {
// 元素不可见 // 隐藏点
(this.canvasObjects ?? this.canvas.getObjects()).forEach(item => {
item.visible = visible;
item.setControlsVisibility({
mt: visible,
mb: visible,
ml: visible,
mr: visible,
bl: visible,
br: visible,
tl: visible,
tr: visible
});
});
this.canvas.renderAll();
}
public resetObjects () {
this.canvasObjects = null;
}
private init () {
this.canvas = new fabric.Canvas(this.$refs.canvas, {
// isDrawingMode: this.editable,
});
this.canvas.on('mouse:down', this.mouseDown);
this.canvas.on('mouse:move', this.mouseMove);
this.canvas.on('mouse:up', this.mouseUp);
this.canvas.on('mouse:dblclick', this.onPolygonDrawEnd);
this.canvas.on('mouse:over', this.mouseOver);
this.canvas.on('mouse:out', this.mouseOut);
this.canvas.on('selection:updated', e => {
this.edit(e.target);
});
this.canvas.on('selection:created', e => {
this.edit(e.target);
});
this.canvas.on('object:modified', this.onChange);
this.canvas.on('object:moving', this.mouseMoving); // 阻止对象移动到画布外面
}
public setDrawType (type) {
// 外部调用此方法,切换画图模式
this.drawType = type;
}
private transformMouse (e): MousePosition {
return e.pointer;
}
private drawLine (position: MousePosition) {
return new fabric.Line([position.x, position.y, position.x, position.y], {
type: DrawType.Line,
objectCaching: false,
hasBorders: false,
selectable: false,
...defaultPolygonStyle
});
}
// 画多边形
private drawPolygon (points: Array<MousePosition>, others?: OthersConfigModel) {
return new fabric.Polygon(points, {
type: DrawType.Polygon,
uuid: createUuid(), // todo
objectCaching: false,
hasBorders: false,
transparentCorners: false,
selectionColor: 'rgba(0,0,0,0)',
...defaultPolygonStyle,
...this.polygonStyle,
...others
});
}
// 画四边形
private drawRect (points: Array<number>, others?: OthersConfigModel) {
const rect = new fabric.Rect({
type: DrawType.Rect,
uuid: createUuid(), // todo
left: points[0],
top: points[1],
width: points[2] || 0,
height: points[3] || 0,
objectCaching: false,
transparentCorners: false,
selectionColor: 'rgba(0,0,0,0)',
lockRotation: true,
...defaultRectStyle,
...this.rectStyle,
...others
});
// eslint-disable-next-line spellcheck/spell-checker
rect.setControlsVisibility({ mtr: false }); // 隐藏旋转点
return rect;
}
private drawCircle (position: MousePosition, others?: OthersConfigModel) {
const circle = new fabric.Circle({
radius: 4,
uuid: createUuid(),
top: position.y,
left: position.x,
...defaultCircleStyle,
...others
});
// eslint-disable-next-line spellcheck/spell-checker
circle.setControlsVisibility({ mtr: false }); // 隐藏旋转点
return circle;
}
private showContent (shape: any, text: string) {
return new fabric.Text(text, {
left: shape.left + 5,
top: shape.top + 10,
fill: 'white',
fontSize: 14
});
}
private getGroup (ellipse: any, text: string) {
return new fabric.Group([ellipse, text], {
uuid: createUuid(),
objectCaching: false,
transparentCorners: false,
selectionColor: 'rgba(0,0,0,0)',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
lockScalingX: true,
lockScalingY: true
});
}
// 鼠标按下
private mouseDown (e) {
// 如果type不为line,则认为正在编辑图形,鼠标点击事件不触发画新图形
if (!this.drawType) {
this.operateAttribute.map(item => {
if (this.selectObject() && this.selectObject()[item]) {
this.selectObject()[item] = !this.rectStyle ? this.selectObject()[item] : this.rectStyle[item];
}
});
return;
}
// 防止在画1的里面画2的时候 影响1
if (this.selectObject() && !this.drawingObject) {
this.operateAttribute.map(item => {
this.selectObject()[item] = true;
});
}
this[`mouseDown${this.drawType}`](this.transformMouse(e));
}
// 鼠标移动
private mouseMove (e) {
if (!this.drawType || !this.drawingObject) {
return;
}
this[`mouseMove${this.drawType}`](this.transformMouse(e));
}
private mouseDownPolygon (position: MousePosition) {
const line = this.drawLine(position);
this.drawingShape.push(line);
this.drawingObject = line;
this.canvas.add(line);
}
private mouseDownRect (position: MousePosition) {
if (this.drawingObject) {
this.canvas.remove(this.drawingObject);
this.canvas.add(this.drawingObject);
this.drawEnd(this.drawingObject);
return;
}
const rect = this.drawRect([position.x, position.y, 0, 0]);
this.drawingObject = rect;
this.canvas.add(rect);
}
private mouseMovePolygon (position: MousePosition) {
if (this.drawingObject) {
this.drawingObject.set({
x2: position.x,
y2: position.y
});
this.canvas.remove(this.drawingObject);
this.canvas.add(this.drawingObject);
}
}
private mouseMoveRect (position: MousePosition) {
if (this.drawingObject) {
this.drawingObject.set({
width: position.x - this.drawingObject.get('left'),
height: position.y - this.drawingObject.get('top')
});
this.canvas.renderAll();
}
}
private mouseUpRect (position: MousePosition) {
// 第一次抬起的时候是有对象的 第二次没有才对 思路:第一个点的鼠标松开的时候重新给draw一下
if (this.drawingObject) {
// 让其可以拖拽 需要确保它有uuid
if (this.selectObject() && this.selectObject().uuid) {
this.operateAttribute.map(item => {
this.selectObject()[item] = false;
});
}
this.canvas.remove(this.drawingObject);
const rect = this.drawRect([this.drawingObject.left, this.drawingObject.top, 0, 0]);
this.drawingObject = rect;
this.canvas.add(rect);
// 设置一下width height 否则松开不动就会有问题
this.drawingObject.set({
width: position.x - this.drawingObject.get('left'),
height: position.y - this.drawingObject.get('top')
});
this.canvas.renderAll();
}
}
private mouseUp (e) {
if (this[`mouseUp${this.drawType}`] && e.pointer) {
this[`mouseUp${this.drawType}`](this.transformMouse(e));
}
}
private mouseMoving (e) {
const padding = -1.5; // 内容距离画布的空白宽度,主动设置
const obj = e.target;
if (obj.currentHeight > obj.canvas.height - padding * 2 || obj.currentWidth > obj.canvas.width - padding * 2) {
return;
}
obj.setCoords();
const objBoundingBox = obj.getBoundingRect();
if (objBoundingBox.top < padding || objBoundingBox.left < padding) {
obj.top = Math.max(obj.top, obj.top - objBoundingBox.top + padding);
obj.left = Math.max(obj.left, obj.left - objBoundingBox.left + padding);
}
if (
objBoundingBox.top + objBoundingBox.height > obj.canvas.height - padding ||
objBoundingBox.left + objBoundingBox.width > obj.canvas.width - padding
) {
obj.top = Math.min(obj.top, obj.canvas.height - objBoundingBox.height + obj.top - objBoundingBox.top - padding);
obj.left = Math.min(obj.left, obj.canvas.width - objBoundingBox.width + obj.left - objBoundingBox.left - padding);
}
}
private mouseOver (e: Event) {
if (!e.target) {
return;
}
this.$emit('handleMouseOver', e.target);
}
private mouseOut (e: Event) {
if (!e.target) {
return;
}
this.$emit('handleMouseOut', e.target);
}
private async onPolygonDrawEnd () {
if (this.drawingObject) {
const points = this.drawingShape.map(line => {
return {
x: line.get('x1'),
y: line.get('y1')
};
});
this.drawingShape.forEach(item => {
this.canvas.remove(item);
});
const polygon = this.drawPolygon(points.splice(0, points.length - 1));
polygon.centerPoints = polygon.getCenterPoint();
this.drawEnd(polygon);
this.canvas.add(polygon);
} else {
this.$emit('copyShape', this.selectObject());
}
}
private async drawEnd (object) {
this.drawingObject = null;
this.drawingShape = [];
if (this.canvasObjects) {
this.canvasObjects.push(object); // 置于顶层/底层之后
}
this.edit(object);
// 设置为当前活跃
this.canvas.setActiveObject(object);
await this.$nextTick();
this.onChange();
this.$emit('change');
}
private edit (object) {
if (object.type === DrawType.Polygon) {
const lastControl = object.points.length - 1;
object.controls = object.points.reduce((a, point, index) => {
a['p' + index] = new fabric.Control({
positionHandler: (dim, finalMatrix, fabricObject) => {
const x = fabricObject.points[index].x - fabricObject.pathOffset.x;
const y = fabricObject.points[index].y - fabricObject.pathOffset.y;
return fabric.util.transformPoint(
{ x: x, y: y },
fabric.util.multiplyTransformMatrices(
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix()
)
);
},
actionHandler: this.anchorWrapper(index > 0 ? index - 1 : lastControl, this.actionHandler),
actionName: 'modifyPolygon',
pointIndex: index
});
return a;
}, {});
} else {
object.cornerStyle = 'circle';
object.controls = fabric.Object.prototype.controls;
}
const ids = (this.canvasObjects ?? this.canvas.getObjects()).map(item => item.uuid).filter(x => x);
this.canvas.requestRenderAll();
this.$emit('editIndex', object.clickIndex ?? ids.indexOf(object.uuid));
}
private anchorWrapper (anchorIndex, fn) {
return function (eventData, transform, x, y) {
const fabricObject = transform.target;
const absolutePoint = fabric.util.transformPoint(
{
x: fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x,
y: fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
},
fabricObject.calcTransformMatrix()
);
const actionPerformed = fn(eventData, transform, x, y);
const polygonBaseSize = fabricObject._getNonTransformedDimensions();
const newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x;
const newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
};
}
private actionHandler (eventData, transform, x, y) {
const polygon = transform.target;
const currentControl = polygon.controls[polygon.__corner];
const mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center');
const polygonBaseSize = polygon._getNonTransformedDimensions();
const size = polygon._getTransformedDimensions(0, 0);
const finalPointPosition = {
x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x + polygon.pathOffset.x,
y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y + polygon.pathOffset.y
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
public selectObject () {
return this.canvas.getActiveObject();
}
public sendToBack () {
if (!this.canvasObjects) {
this.canvasObjects = this.canvas.getObjects();
}
this.canvas.sendToBack(this.selectObject());
}
public setTop () {
if (!this.canvasObjects) {
this.canvasObjects = this.canvas.getObjects();
}
this.canvas.bringToFront(this.selectObject());
}
public async onLoadStereo () {
this.updateCanvasStyle(this.width, this.height);
}
public async updateCanvasStyle (width: number, height: number) {
// 改变canvas 的宽高
this.canvas.setWidth(width);
this.canvas.setHeight(height);
}
private beforeDestroy () {
this.canvas.removeListeners();
this.removeAll();
}
private mounted () {
this.init();
}
}
</script>
<style lang="scss" scoped>
.drawer {
position: relative;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
width: 100%;
height: calc(100% - 70px);
.canvans,
.container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
}
</style>
<style lang="scss">
.canvas-container {
z-index: 2;
width: 100% !important; // 设置这几个百分比用来解决窗口尺寸变化 框也变化
height: 100% !important;
}
.lower-canvas,.upper-canvas {
width: 100% !important;
height: 100% !important;
}
</style>
组件代码ImageDrawer.vue
dropImage 可以拖拽图片
使用组件的时候需要传入shapes
<template>
<div @mousedown.prevent="dropImage(arguments[0])">
<drawer
ref="drawer"
v-bind="$attrs"
:width="width"
:height="height"
v-on="$listeners"
@onChange="onChange"
@contextmenu.prevent.native="openMenu($event)"
>
<basic-img
class="img"
:style="`width: ${width}px; height: ${height}px;${maxWidth ? `maxWidth:${maxWidth}px` : ''}`"
v-bind="$attrs"
@load="imageLoaded"
/>
<ul
v-show="visible"
:style="contextmenuStyle"
class="contextmenu"
>
<li @click="setBack">
置于底层
</li>
<li @click="setTop">
置于顶层
</li>
</ul>
</drawer>
</div>
</template>
<script lang='ts'>
import Vue from 'vue';
import { Component, Ref, Prop, PropSync, Watch } from 'vue-property-decorator';
import Drawer, { Shape, DrawType, MousePosition, RectModel, OthersConfigModel } from './Drawer.vue';
import BasicImg from '@/components/img/BasicImg.vue';
export type ShapeModel = Shape
@Component({
components: {
Drawer,
BasicImg
}
})
export default class ImageDrawer extends Vue {
@Ref()
private readonly drawer !: Drawer
@Prop({
type: [String, Number],
required: false,
default: 780
})
private width!: string | number; // innerHeight
@Prop({
type: [String, Number],
required: false,
default: 0
})
private maxWidth!: string | number; // innerHeight
@Prop({
type: [String, Number],
required: false,
default: 580
})
private height!: string | number;
@Prop({
type: Boolean,
required: false,
default: false
})
private canDropImage!: boolean;
@PropSync('shapes', { type: Array })
private bindShapes!: Array<Shape>;
@Prop({
type: Array,
required: false,
default: () => []
})
private othersShapes!: Array<Shape>;
private imgInfo: any={}; // 图片信息
private xRate=0;
private yRate=0;
private contextmenuStyle: any={}
private visible=false;
private odiv: any=null;
private editAble=false;
private onChange (object: any) {
this.bindShapes = object.map(item => {
let points: any = [];
if (item.type === DrawType.Polygon && item.centerPoints) {
const pointsX = item.getCenterPoint().x - item.centerPoints.x;
const pointsY = item.getCenterPoint().y - item.centerPoints.y;
points = item.points.map(item => {
return {
x: item.x + pointsX,
y: item.y + pointsY
};
});
}
return {
type: item.type,
// 放大坐标
points: item.type === DrawType.Polygon ? this.enlargePolygon(points) : this.enlargeRect(item),
styles: {
...item
}
};
});
}
// 将点位比例放大 渲染计算之后给后台
private enlargePolygon (points: MousePosition[]) {
return points.map(item => {
return [item.x / this.xRate, item.y / this.yRate];
});
}
private enlargeRect (item: RectModel) {
item.width = item.width * (item.scaleX || 1);
item.height = item.height * (item.scaleY || 1);
// 解决从各个方向画的问题
const convertItem: RectModel = this.getConvertPoints(item);
const { left, top, width, height } = convertItem;
// 计算完成之后需要把宽度设回去 不然会影响页面上的效果
item.width = item.width / (item.scaleX || 1);
item.height = item.height / (item.scaleY || 1);
return [
[
left / this.xRate,
top / this.yRate
],
[
(left + width) / this.xRate,
(top + height) / this.yRate
]
];
}
private getConvertPoints (item: RectModel) {
if (item.width < 0 && item.height < 0) {
// 右下角画
item.left = item.left + item.width;
item.top = item.top + item.height;
item.width = -item.width;
item.height = -item.height;
} else if (item.width < 0) {
// 右上角画
item.left = item.left + item.width;
item.width = -item.width;
} else if (item.height < 0) {
// 左下角画
item.top = item.top + item.height;
item.height = -item.height;
}
return item;
}
private async imageLoaded (e) {
// 获取图片原始宽高 // 实际宽高
this.imgInfo = e.target;
await this.$nextTick();
this.drawer && this.drawer.updateCanvasStyle(this.imgInfo.width, this.imgInfo.height);
this.$emit('load', e);
// 最终换算比例
this.xRate = e.target.width / e.target.naturalWidth;
this.yRate = e.target.height / e.target.naturalHeight;
// 得到imgInfo
await this.initShapes();
}
public async updateCanvasStyle (width: number, height: number, xRate?: number, yRate?: number) {
await this.removeAll(); // 防止有些时候执行多次
this.drawer && this.drawer.updateCanvasStyle(width, height);
this.xRate = xRate || this.xRate;
this.yRate = yRate || this.yRate;
await this.initShapes();
}
public async initShapes (selectFirst = true) {
await this.removeAll();
// if(this.bindShapes && !this.bindShapes.length && this.othersShapes && !this.othersShapes.length){
// return
// }
// 不同的type 缩小的比例不一样 缩小比例
await this.othersShapes.map(item => {
if (item.type === DrawType.Rect) {
this.narrowRect(item.points as number[], item.others as OthersConfigModel);
} else if (item.type === DrawType.Polygon) {
this.narrowPolygon(item.points as number[], item.others as OthersConfigModel);
}
});
await this.bindShapes.map(item => {
if (item.type === DrawType.Rect) {
this.narrowRect(item.points as number[], item.others as OthersConfigModel, item.content as string);
} else if (item.type === DrawType.Polygon) {
this.narrowPolygon(item.points as number[], item.others as OthersConfigModel, item.content as string);
}
});
this.drawer && this.drawer.resetObjects(); // 重新绘制的时候就reset
if (selectFirst) {
// init选择为第一个
this.setActiveObjectByIndex(0);
}
}
// 将点位比例缩小 从后台传来渲染 content 3d点云处使用
private narrowPolygon (points: number[], others?: OthersConfigModel, content?: string) {
const pointsList = points.map(item => {
return {
x: item[0] * this.xRate,
y: item[1] * this.yRate
};
});
if (content) {
this.drawer.showPolygonGroup(pointsList, content, others);
} else {
this.drawer && this.drawer.narrowPolygon(pointsList, others);
}
}
private narrowRect (points: number[], others?: OthersConfigModel, content?: string) {
const pointsList = [
points[0][0] * this.xRate,
points[0][1] * this.yRate,
(points[1][0] - points[0][0]) * this.xRate,
(points[1][1] - points[0][1]) * this.yRate
];
if (content) {
this.drawer.showRectGroup(pointsList, content, others);
} else {
this.drawer && this.drawer.narrowRect(pointsList, others);
}
}
public narrowCircle (points: number[], others?: OthersConfigModel) {
const point = {
x: points[0] * this.xRate,
y: points[1] * this.yRate
};
this.drawer.narrowCircle(point, others);
}
public removeAll () {
this.drawer.removeAll();
}
public setDrawType (drawType: string) {
this.editAble = drawType !== '';
this.drawer.setDrawType(drawType);
}
public removeItemByIndex (index: number) {
this.drawer.removeItemByIndex(index);
}
public setActiveObjectByIndex (index: number) {
this.drawer && this.drawer.setActiveObjectByIndex(index);
}
public setVisible (visible: boolean) {
this.drawer.setVisible(visible);
}
// 鼠标右键
private async openMenu (e) {
this.contextmenuStyle = {
left: e.offsetX + 'px',
top: e.offsetY + 'px'
};
this.visible = true;
}
private setBack () {
this.drawer.sendToBack();
}
private setTop () {
this.drawer.setTop();
}
private closeMenu () {
this.visible = false;
}
// 放大缩小
public setTransform (multiples: number) {
(this.$el as any).style.transform = `scale(${multiples})`;
}
// 拖拽
public dropImage (e: any) {
if (!e || this.editAble || !this.canDropImage) {
return;
}
this.odiv = this.drawer.$el; // 获取目标元素
// 算出鼠标相对元素的位置
const disX = e.clientX - this.odiv.offsetLeft;
const disY = e.clientY - this.odiv.offsetTop;
document.onmousemove = (e) => { // 鼠标按下并移动的事件
// 用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
const left = e.clientX - disX;
const top = e.clientY - disY;
// 移动当前元素
this.odiv.style.left = left + 'px';
this.odiv.style.top = top + 'px';
};
document.onmouseup = (e) => {
document.onmousemove = null;
document.onmouseup = null;
this.$emit('onmouseup', this.odiv.style.left, this.odiv.style.top);
};
}
// 回到原始位置 一起拖拽
public setDrawerPosition (left = 0, top = 0) {
this.odiv = this.drawer.$el; // 获取目标元素
if (this.odiv) {
this.odiv.style.left = left;
this.odiv.style.top = top;
}
}
@Watch('visible')
private visibleChange () {
if (this.visible) { // 显示的时候 添加一个点击事件 用于隐藏右键的内容
window.addEventListener('click', this.closeMenu);
} else {
window.removeEventListener('click', this.closeMenu);
}
}
}
</script>
<style lang='scss' scoped>
@import '~@/css/variable';
.contextmenu {
min-width: 86px;
margin: 0;
background: #eeeded;
opacity: 0.9;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px;
border-radius: 4px;
font-size: 12px;
color: #505050;
border: 1px solid $color-border;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
}
.contextmenu li {
margin: 0;
padding: 2px 10px;
border-radius: 2px;
cursor: pointer;
}
.contextmenu li:hover {
background: $color-button-disabled-hover;
color: $color-basic;
}
::v-deep .el-image__placeholder {
background: transparent;
}
</style>
fabric版本5.3.0
多边形选中的时候 点位显示有问题
参考官方例子
所以在多边形这里需要单独处理一下,我是在设置选中的时候 做的处理【需要说明这时候我是用的是vue3】,核心代码如下
// 根据id设置当前活跃的框
const setActiveObjectById = (id: number) => {
(canvasObjects.value ?? canvas.value.getObjects()).forEach((item: any) => {
if (item.shapeId === id) {
canvas.value?.setActiveObject(item);
if (item.type === DrawType.Polygon) {
item.controls = item.points.reduce(
function (
// eslint-disable-next-line spellcheck/spell-checker
acc: { [x: string]: fabric.Control },
point: any,
index: number
) {
// eslint-disable-next-line spellcheck/spell-checker
acc['p' + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
pointIndex: index
} as any);
// eslint-disable-next-line spellcheck/spell-checker
return acc;
} as any,
{}
);
}
}
});
};
function polygonPositionHandler(
this: { positionHandler: (dim: any, finalMatrix: any, fabricObject: any) => fabric.Point; pointIndex: number },
dim: any,
finalMatrix: any,
fabricObject: any
) {
var x = fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y;
return fabric.util.transformPoint(
{ x: x, y: y } as any,
fabric.util.multiplyTransformMatrices(fabricObject.canvas.viewportTransform, fabricObject.calcTransformMatrix())
);
}