需求在一張圖片上標(biāo)注矩形和多邊形,支持回顯;
fabric版本:4.6.0;
Fabric.js 是一個功能強(qiáng)大且操作簡單的 Javascript HTML5 canvas 工具庫。
官方文檔
參考鏈接
組件代碼drawer.vue
createUuid 是為了讓每一個圖形有自己的id;方便用于獲取用戶點擊的那個圖形等操作;
defaultRectStyle、defaultPolygonStyle是自己定義的一些默認(rèn)的樣式,比如顏色,邊框大小等;
<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; // 禁止選中當(dāng)前元素
lockMovementX: boolean; // 禁止元素移動
lockMovementY: boolean;
lockScalingX: boolean; // 禁止元素縮
lockScalingY: boolean;
standard_item_id: number;
visible: boolean; // 設(shè)置元素不可見
}
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; // 用戶點擊置于頂層/底層 導(dǎo)致順序變化 ids變化 index也變化
// 改變時候觸發(fā)
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);
});
}
// 設(shè)置為當(dāng)前活躍
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) {
// 外部調(diào)用此方法,切換畫圖模式
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 }); // 隱藏旋轉(zhuǎn)點
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 }); // 隱藏旋轉(zhuǎn)點
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
});
}
// 鼠標(biāo)按下
private mouseDown (e) {
// 如果type不為line,則認(rèn)為正在編輯圖形,鼠標(biāo)點擊事件不觸發(fā)畫新圖形
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));
}
// 鼠標(biāo)移動
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) {
// 第一次抬起的時候是有對象的 第二次沒有才對 思路:第一個點的鼠標(biāo)松開的時候重新給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);
// 設(shè)置一下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; // 內(nèi)容距離畫布的空白寬度,主動設(shè)置
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);
// 設(shè)置為當(dāng)前活躍
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; // 設(shè)置這幾個百分比用來解決窗口尺寸變化 框也變化
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,
// 放大坐標(biāo)
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;
// 計算完成之后需要把寬度設(shè)回去 不然會影響頁面上的效果
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(); // 防止有些時候執(zhí)行多次
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);
}
// 鼠標(biāo)右鍵
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; // 獲取目標(biāo)元素
// 算出鼠標(biāo)相對元素的位置
const disX = e.clientX - this.odiv.offsetLeft;
const disY = e.clientY - this.odiv.offsetTop;
document.onmousemove = (e) => { // 鼠標(biāo)按下并移動的事件
// 用鼠標(biāo)的位置減去鼠標(biāo)相對元素的位置,得到元素的位置
const left = e.clientX - disX;
const top = e.clientY - disY;
// 移動當(dāng)前元素
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; // 獲取目標(biāo)元素
if (this.odiv) {
this.odiv.style.left = left;
this.odiv.style.top = top;
}
}
@Watch('visible')
private visibleChange () {
if (this.visible) { // 顯示的時候 添加一個點擊事件 用于隱藏右鍵的內(nèi)容
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
多邊形選中的時候 點位顯示有問題
參考官方例子文章來源:http://www.zghlxwxcb.cn/news/detail-474579.html
所以在多邊形這里需要單獨處理一下,我是在設(shè)置選中的時候 做的處理【需要說明這時候我是用的是vue3】,核心代碼如下文章來源地址http://www.zghlxwxcb.cn/news/detail-474579.html
// 根據(jù)id設(shè)置當(dāng)前活躍的框
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())
);
}
到了這里,關(guān)于利用fabric繪畫矩形和多邊形的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!