2026-03-09 13:44:32 +08:00
|
|
|
import { fabric } from 'fabric-with-all'
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
import { createCanvas } from '../tools/canvasFactory'
|
2026-03-09 14:20:44 +08:00
|
|
|
import { AnimationManager } from './AnimationManager'
|
2026-03-09 13:44:32 +08:00
|
|
|
import { detectDeviceType } from '../tools/index'
|
|
|
|
|
import { CanvasEventManager } from "./events/CanvasEventManager";
|
|
|
|
|
import { OperationType } from '../tools/layerHelper'
|
|
|
|
|
|
2026-03-13 11:18:36 +08:00
|
|
|
// 自定义画布转对象属性
|
|
|
|
|
fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail"];
|
|
|
|
|
fabric.Object.prototype.toObject_ = fabric.Object.prototype.toObject
|
|
|
|
|
fabric.Object.prototype.toObject = function () {
|
|
|
|
|
const args = [...arguments]
|
|
|
|
|
const arr = [...fabric.Object.prototype.customProperties]
|
|
|
|
|
args.forEach(v => {
|
|
|
|
|
if (typeof v === 'string') {
|
|
|
|
|
arr.push(v)
|
|
|
|
|
} else if (Array.isArray(v)) {
|
|
|
|
|
arr.push(...v)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return this.toObject_(arr)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 13:44:32 +08:00
|
|
|
interface CanvasInitOptions {
|
|
|
|
|
canvasRef: any
|
|
|
|
|
canvasViewWidth?: number
|
|
|
|
|
canvasViewHeight?: number
|
|
|
|
|
canvasWidth?: number
|
|
|
|
|
canvasHeight?: number
|
|
|
|
|
}
|
|
|
|
|
export class CanvasManager {
|
|
|
|
|
stateManager: any
|
2026-03-09 16:45:30 +08:00
|
|
|
layerManager: any
|
|
|
|
|
animationManager: any
|
|
|
|
|
eventManager: any
|
2026-03-09 13:44:32 +08:00
|
|
|
deviceInfo: any
|
|
|
|
|
canvas: any
|
|
|
|
|
canvasViewWidth: number
|
|
|
|
|
canvasViewHeight: number
|
|
|
|
|
canvasWidth: number
|
|
|
|
|
canvasHeight: number
|
|
|
|
|
currentZoom: any
|
|
|
|
|
constructor(options) {
|
|
|
|
|
this.stateManager = options.stateManager;
|
|
|
|
|
this.deviceInfo = detectDeviceType();
|
|
|
|
|
this.currentZoom = ref(100)
|
|
|
|
|
}
|
|
|
|
|
setCanvasViewSize(options) {
|
|
|
|
|
this.canvasViewWidth = options.canvasViewWidth || 1920
|
|
|
|
|
this.canvasViewHeight = options.canvasViewHeight || 1080
|
2026-03-12 16:22:40 +08:00
|
|
|
this.canvas.setWidth(this.canvasViewWidth)
|
|
|
|
|
this.canvas.setHeight(this.canvasViewHeight)
|
2026-03-09 13:44:32 +08:00
|
|
|
}
|
2026-03-13 14:08:40 +08:00
|
|
|
/** 初始化画布 */
|
|
|
|
|
async initCanvas(options: CanvasInitOptions) {
|
2026-03-09 16:45:30 +08:00
|
|
|
this.layerManager = this.stateManager.layerManager
|
2026-03-09 13:44:32 +08:00
|
|
|
this.canvasWidth = options.canvasWidth || 750
|
|
|
|
|
this.canvasHeight = options.canvasHeight || 600
|
|
|
|
|
this.canvas = createCanvas(options.canvasRef.value, {
|
|
|
|
|
preserveObjectStacking: true,
|
|
|
|
|
enableRetinaScaling: true,
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
})
|
2026-03-12 16:22:40 +08:00
|
|
|
this.setCanvasViewSize(options)
|
2026-03-09 13:44:32 +08:00
|
|
|
this.canvas.clipPath = new fabric.Rect({
|
|
|
|
|
left: 0,
|
|
|
|
|
top: 0,
|
|
|
|
|
width: this.canvasWidth,
|
|
|
|
|
height: this.canvasHeight
|
|
|
|
|
})
|
|
|
|
|
// 画布居中
|
|
|
|
|
const canvasX = this.canvasViewWidth / 2 - this.canvasWidth / 2
|
|
|
|
|
const canvasY = this.canvasViewHeight / 2 - this.canvasHeight / 2
|
|
|
|
|
this.canvas.viewportTransform = [1, 0, 0, 1, canvasX, canvasY]
|
2026-03-11 15:34:56 +08:00
|
|
|
|
2026-03-12 11:40:48 +08:00
|
|
|
// 动画管理器
|
|
|
|
|
this.animationManager = new AnimationManager(this.canvas, {
|
|
|
|
|
currentZoom: this.currentZoom,
|
|
|
|
|
canvasManager: this,
|
|
|
|
|
wheelThrottleTime: 15, // 降低滚轮事件节流时间,提高响应性
|
|
|
|
|
defaultEase: "power2.lin",
|
|
|
|
|
defaultDuration: 0.3, // 缩短默认动画时间
|
|
|
|
|
});
|
|
|
|
|
this.setupCanvasEvents()
|
|
|
|
|
this.setupBrushEvents()
|
|
|
|
|
|
2026-03-13 14:08:40 +08:00
|
|
|
|
|
|
|
|
this.stateManager.setIsRecord(false)
|
2026-03-09 13:44:32 +08:00
|
|
|
// 创建矩形
|
2026-03-13 14:08:40 +08:00
|
|
|
const rect = await this.layerManager.createRectLayer({
|
2026-03-11 15:34:56 +08:00
|
|
|
left: 400,
|
|
|
|
|
top: 100,
|
2026-03-09 13:44:32 +08:00
|
|
|
})
|
|
|
|
|
//创建圆形
|
2026-03-13 14:08:40 +08:00
|
|
|
const circle = await this.layerManager.createCircleLayer({
|
2026-03-09 13:44:32 +08:00
|
|
|
left: 200,
|
|
|
|
|
top: 200,
|
2026-03-09 16:45:30 +08:00
|
|
|
})
|
|
|
|
|
// 文字
|
2026-03-13 14:08:40 +08:00
|
|
|
const text = await this.layerManager.createTextLayer('Hello World');
|
2026-03-09 16:45:30 +08:00
|
|
|
this.layerManager.updateLayers()
|
2026-03-11 15:34:56 +08:00
|
|
|
this.layerManager.setActiveID(text.info.id)
|
2026-03-13 14:08:40 +08:00
|
|
|
this.stateManager.setIsRecord(true)
|
2026-03-12 11:40:48 +08:00
|
|
|
this.stateManager.recordState()
|
2026-03-12 15:51:18 +08:00
|
|
|
// this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
|
2026-03-11 15:34:56 +08:00
|
|
|
}
|
|
|
|
|
/** 画布添加对象 */
|
2026-03-13 14:08:40 +08:00
|
|
|
async add(obj: any, isUpdate = true) {
|
2026-03-11 15:34:56 +08:00
|
|
|
this.canvas.add(obj)
|
2026-03-13 14:08:40 +08:00
|
|
|
const id = obj?.info?.id || ""
|
2026-03-11 15:34:56 +08:00
|
|
|
if (isUpdate) {
|
|
|
|
|
this.layerManager.updateLayers()
|
|
|
|
|
this.renderAll()
|
2026-03-13 14:08:40 +08:00
|
|
|
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
|
|
|
|
this.stateManager.recordState()
|
2026-03-11 15:34:56 +08:00
|
|
|
}
|
2026-03-09 13:44:32 +08:00
|
|
|
}
|
2026-03-12 15:51:18 +08:00
|
|
|
/** 画布移除对象 */
|
|
|
|
|
remove(obj: any, isUpdate = true) {
|
|
|
|
|
this.canvas.remove(obj)
|
|
|
|
|
if (isUpdate) {
|
|
|
|
|
this.layerManager.updateLayers()
|
|
|
|
|
this.renderAll()
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-11 15:34:56 +08:00
|
|
|
|
|
|
|
|
/** 设置画布事件 */
|
2026-03-09 13:44:32 +08:00
|
|
|
setupCanvasEvents() {
|
|
|
|
|
// 创建画布事件管理器
|
|
|
|
|
this.eventManager = new CanvasEventManager(this.canvas, {
|
|
|
|
|
canvasManager: this,
|
|
|
|
|
animationManager: this.animationManager,
|
|
|
|
|
toolManager: this.stateManager.toolManager,
|
2026-03-11 15:34:56 +08:00
|
|
|
layerManager: this.stateManager.layerManager,
|
2026-03-12 11:40:48 +08:00
|
|
|
stateManager: this.stateManager,
|
2026-03-09 13:44:32 +08:00
|
|
|
});
|
|
|
|
|
// 设置动画交互效果
|
|
|
|
|
this.animationManager.setupInteractionAnimations();
|
|
|
|
|
}
|
2026-03-11 15:34:56 +08:00
|
|
|
/** 设置激活对象 */
|
2026-03-13 11:18:36 +08:00
|
|
|
setActiveObjectById(id: string) {
|
2026-03-11 15:34:56 +08:00
|
|
|
const obj = this.getObjectById(id)
|
|
|
|
|
if (obj) this.canvas.setActiveObject(obj)
|
|
|
|
|
this.renderAll()
|
|
|
|
|
}
|
2026-03-09 13:44:32 +08:00
|
|
|
resetZoom() {
|
|
|
|
|
this.animationManager.resetZoom()
|
|
|
|
|
}
|
2026-03-09 16:45:30 +08:00
|
|
|
getObjects() {
|
|
|
|
|
return this.canvas.getObjects() || []
|
|
|
|
|
}
|
|
|
|
|
getObjectById(id: string) {
|
2026-03-12 15:51:18 +08:00
|
|
|
return this.getObjects().find((item: any) => item?.info?.id === id)
|
2026-03-09 16:45:30 +08:00
|
|
|
}
|
|
|
|
|
renderAll() {
|
|
|
|
|
this.canvas.renderAll()
|
|
|
|
|
}
|
2026-03-09 13:44:32 +08:00
|
|
|
|
2026-03-09 16:45:30 +08:00
|
|
|
deleteObjectById(id: string) {
|
|
|
|
|
const object = this.getObjectById(id)
|
|
|
|
|
if (object) {
|
|
|
|
|
this.canvas.remove(object)
|
|
|
|
|
this.layerManager.updateLayers()
|
|
|
|
|
this.renderAll()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 拖拽排序
|
|
|
|
|
dragSort(id, newIndex) {
|
|
|
|
|
this.canvas.moveTo(this.getObjectById(id), newIndex)
|
|
|
|
|
this.layerManager.updateLayers()
|
|
|
|
|
this.renderAll()
|
2026-03-13 14:08:40 +08:00
|
|
|
this.stateManager.recordState()
|
2026-03-09 16:45:30 +08:00
|
|
|
}
|
2026-03-09 13:44:32 +08:00
|
|
|
|
2026-03-11 15:34:56 +08:00
|
|
|
/** 画笔事件 */
|
|
|
|
|
setupBrushEvents() {
|
|
|
|
|
this.canvas.onBrushImageConverted = async (fabricImage) => {
|
|
|
|
|
const currentTool = this.stateManager.toolManager.currentTool.value;
|
|
|
|
|
if (currentTool === OperationType.DRAW) {
|
|
|
|
|
this.handleDrawImage(fabricImage)
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
/** 处理绘制图像 */
|
|
|
|
|
handleDrawImage(fabricImage: fabric.Object) {
|
|
|
|
|
const activeID = this.stateManager.layerManager.activeID.value
|
|
|
|
|
const activeLayer = this.getObjectById(activeID)
|
|
|
|
|
if (activeLayer) {
|
|
|
|
|
this.layerManager.imageMergeToLayer(activeLayer, fabricImage)
|
|
|
|
|
} else {
|
2026-03-13 14:08:40 +08:00
|
|
|
const emptyLayer = this.layerManager.createEmptyLayer(false);
|
2026-03-11 15:34:56 +08:00
|
|
|
this.layerManager.setActiveID(emptyLayer.info.id, false)
|
|
|
|
|
this.layerManager.imageMergeToLayer(emptyLayer, fabricImage)
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 导出画布为JSON */
|
|
|
|
|
getCanvasJSON() {
|
2026-03-13 11:18:36 +08:00
|
|
|
const json = this.canvas.toJSON()
|
2026-03-11 15:34:56 +08:00
|
|
|
return JSON.stringify(json)
|
|
|
|
|
}
|
|
|
|
|
/** 加载画布JSON */
|
|
|
|
|
loadJSON(json: string, callback?: (success: boolean) => void) {
|
|
|
|
|
let jsonObj = null;
|
|
|
|
|
try {
|
|
|
|
|
jsonObj = JSON.parse(json)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('JSON解析错误:', error)
|
|
|
|
|
}
|
|
|
|
|
if (!jsonObj) return callback?.(false);
|
|
|
|
|
this.canvas.loadFromJSON(jsonObj, () => {
|
|
|
|
|
this.layerManager.updateLayers()
|
|
|
|
|
this.renderAll()
|
|
|
|
|
callback?.(true)
|
|
|
|
|
})
|
2026-03-09 13:44:32 +08:00
|
|
|
|
2026-03-11 15:34:56 +08:00
|
|
|
}
|
2026-03-12 11:40:48 +08:00
|
|
|
dispose() {
|
|
|
|
|
this.animationManager?.dispose()
|
|
|
|
|
this.eventManager?.dispose()
|
|
|
|
|
}
|
2026-03-09 13:44:32 +08:00
|
|
|
}
|