import { fabric } from 'fabric-with-all' import { ref, computed } from 'vue' import { createCanvas } from '../tools/canvasFactory' import { AnimationManager } from './AnimationManager' import { detectDeviceType } from '../tools/index' import { CanvasEventManager } from "./events/CanvasEventManager"; import { OperationType } from '../tools/layerHelper' import { cloneObjects } from '../tools/canvasMethod' import { createId } from '../../tools/tools' import md5 from 'md5' // 自定义画布转对象属性 fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail", "absolutePositioned"]; 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) } }) const object = this.toObject_(arr) if (object.info) { let lock = !!object.info.lock object.evented = !lock object.selectable = !lock } return object } interface CanvasInitOptions { canvasRef: any canvasViewWidth?: number canvasViewHeight?: number canvasWidth?: number canvasHeight?: number url?: string } export class CanvasManager { stateManager: any layerManager: any animationManager: any eventManager: any deviceInfo: any canvas: any currentZoom: any uniqueId: string constructor(options) { this.stateManager = options.stateManager; this.uniqueId = md5(options.props.config.url || Date.now()); this.deviceInfo = detectDeviceType(); this.currentZoom = ref(100) } onMounted() { } getCanvasSize() { return { canvasViewWidth: this.canvas.width, canvasViewHeight: this.canvas.height, canvasWidth: this.canvas.clipPath.width, canvasHeight: this.canvas.clipPath.height, } } setCanvasViewSize(options) { var canvasViewWidth = options.canvasViewWidth || 1920 var canvasViewHeight = options.canvasViewHeight || 1080 this.canvas.setWidth(canvasViewWidth) this.canvas.setHeight(canvasViewHeight) } /** 初始化画布 */ async initCanvas(options: CanvasInitOptions) { this.layerManager = this.stateManager.layerManager var canvasWidth = options.canvasWidth || 750 var canvasHeight = options.canvasHeight || 600 var image = null; if (options.url) { await new Promise((resolve) => { fabric.Image.fromURL(options.url, async (img) => { canvasWidth = img.width canvasHeight = img.height img.set({ left: 0, top: 0, scaleX: 1, scaleY: 1, evented: false, selectable: false, info: { id: createId("image"), name: "图片图层", lock: true, } }) image = img resolve(img) }, { crossOrigin: 'anonymous' }) }) } this.canvas = createCanvas(options.canvasRef.value, { preserveObjectStacking: true, enableRetinaScaling: true, backgroundColor: '#fff', }) if (image) { this.canvas.add(image) await this.layerManager.updateLayerThumbnailsById(image.info.id) } this.setCanvasViewSize(options) this.canvas.clipPath = new fabric.Rect({ left: 0, top: 0, width: canvasWidth, height: canvasHeight }) // 动画管理器 this.animationManager = new AnimationManager(this.canvas, { currentZoom: this.currentZoom, canvasManager: this, wheelThrottleTime: 15, // 降低滚轮事件节流时间,提高响应性 defaultEase: "power2.lin", defaultDuration: 0.3, // 缩短默认动画时间 }); this.setupCanvasEvents() this.setupBrushEvents() /** 测试-开始 */ // this.stateManager.setIsRecord(false) // const groupObject = await this.layerManager.createGroupLayer() // const parentId = groupObject.info.id // const rect = await this.layerManager.createRectLayer({ left: 200, info: { parentId } }) // const star = await this.layerManager.createStarLayer({ left: 400, info: { parentId } }) // const arrow = await this.layerManager.createArrowLayer({ left: 600, info: { parentId } }) // await this.layerManager.createGroupLayer() // this.layerManager.setActiveID(rect.info.id) // this.stateManager.setIsRecord(true) /** 测试-结束 */ this.resetZoom(false, true)// 画布居中 this.stateManager.toolManager.setTool(OperationType.SELECT) this.layerManager.updateLayers() this.stateManager.recordState() } /** 画布添加对象 */ async add(obj: any, isRecord = true) { this.canvas.add(obj) const id = obj?.info?.id || "" if (id) { await this.layerManager.updateLayers(!!obj.info.parentId) this.renderAll() await this.layerManager.updateLayerThumbnailsById(id) } if (isRecord) this.stateManager.recordState() } /** 画布移除对象 */ remove(obj: any, isUpdate = true) { this.canvas.remove(obj) if (isUpdate) { this.layerManager.updateLayers() this.renderAll() } } /** 更新子图层裁剪区域 */ async updateSubLayerClipPath() { const objects = this.getObjects().filter((v: any) => v.type !== "group" && !!v.info?.id); for (let i = 0; i < objects.length; i++) { let object = objects[i] if (object.clipPath) object.set({ clipPath: null }) let group = this.getObjectById(object.info.parentId) if (!group) continue let path = group.clipPath if (!path) continue let clipPath = await cloneObjects([path]).then((v) => v[0]) clipPath.set({ absolutePositioned: true, }) object.set({ clipPath }) } this.renderAll() } /** 排序画布对象 */ async sortObjectByIds(ids: string[], isRecord?: boolean) { ids.forEach((id, index) => { this.canvas.moveTo(this.getObjectById(id), index) }) await this.updateSubLayerClipPath() this.renderAll() if (isRecord) this.stateManager.recordState() } /** 设置画布事件 */ setupCanvasEvents() { // 创建画布事件管理器 this.eventManager = new CanvasEventManager(this.canvas, { canvasManager: this, animationManager: this.animationManager, toolManager: this.stateManager.toolManager, layerManager: this.stateManager.layerManager, stateManager: this.stateManager, }); // 设置动画交互效果 this.animationManager.setupInteractionAnimations(); } /** 设置激活对象 */ setActiveObjectById(id: string) { this.discardActiveObject() const obj = this.getObjectById(id) if (!obj) return if (obj.type === "group") { const objects = []; this.getObjects().forEach((item: any) => { if (item?.info?.parentId === id) objects.push(item) }) if (objects.length > 0) this.canvas.setActiveObject(new fabric.ActiveSelection(objects, { canvas: this.canvas })); } else { if (obj.evented) this.canvas.setActiveObject(obj) } this.renderAll() } resetZoom(animated = true, adaptive = true) { this.animationManager.resetZoom(animated, adaptive) } // 使用动画管理器的缩放方法 animateZoom(point, targetZoom, options = {}) { this.animationManager.animateZoom(point, targetZoom, options); } zoomIn() { const currentZoom = this.canvas.getZoom() const newZoom = Math.min(currentZoom + 0.1, 20) // 增加20%,最大20倍 // 使用画布中心作为缩放点 const centerPoint = { x: this.canvas.width / 2, y: this.canvas.height / 2 } this.animateZoom(centerPoint, newZoom) } zoomOut() { const currentZoom = this.canvas.getZoom() const newZoom = Math.max(currentZoom - 0.1, 0.1) // 减少20%,最小0.1倍 // 使用画布中心作为缩放点 const centerPoint = { x: this.canvas.width / 2, y: this.canvas.height / 2 } this.animateZoom(centerPoint, newZoom) } getObjects() { return this.canvas.getObjects() || [] } getObjectById(id: string) { return this.getObjects().find((item: any) => item?.info?.id === id) } /** 获取选中对象 */ getSelectedObject() { return this.canvas.getActiveObject() } renderAll() { this.canvas.renderAll() } deleteObjectById(id: string, isUpdate = true) { const object = this.getObjectById(id) if (object) { this.canvas.remove(object) if (isUpdate) this.layerManager.updateLayers() this.renderAll() } } /** 取消选中对象 */ discardActiveObject() { this.canvas.discardActiveObject() this.renderAll() } /** 画笔事件 */ setupBrushEvents() { this.canvas.onBrushImageConverted = async (fabricImage) => { const currentTool = this.stateManager.toolManager.currentTool.value; if (currentTool === OperationType.DRAW) { this.handleDrawImage(fabricImage) } return true }; } /** 处理绘制图像 */ async handleDrawImage(fabricImage: fabric.Object) { const activeID = this.stateManager.layerManager.activeID.value const activeLayer = this.getObjectById(activeID) if (activeLayer && activeLayer.fill?.repeat !== "repeat") { this.layerManager.imageMergeToLayer(activeLayer, fabricImage) } else { const emptyLayer = await this.layerManager.createEmptyLayer(false); this.layerManager.setActiveID(emptyLayer.info.id, false) this.layerManager.imageMergeToLayer(emptyLayer, fabricImage) } return true } /** 导出画布为JSON */ getCanvasJSON() { const json = this.canvas.toJSON() return JSON.stringify(json) } /** 加载画布JSON */ loadJSON(json: string, rerecord = true) { return new Promise((resolve) => { let jsonObj = null; try { jsonObj = JSON.parse(json) } catch (error) { console.error('JSON解析错误:', error) } if (!jsonObj) return resolve(false) if (jsonObj.uniqueId) this.uniqueId = jsonObj.uniqueId this.canvas.loadFromJSON(jsonObj, () => { if (rerecord) this.stateManager.clearState() this.resetZoom(false) this.layerManager.updateLayers() this.renderAll() if (rerecord) this.stateManager.recordState() resolve(true) }) }) } /** 导出画布为处理图片的JSON */ getCanvasDisUrlJSON() { const canvas = this.canvas.toJSON() const images = {}; const create = (url, key) => { const key_ = `xxxxx_${this.uniqueId}_${md5(key)}_xxxxx`; images[key_] = url return key_ } canvas.uniqueId = this.uniqueId canvas.objects.forEach((object: any) => { const id = object.info?.id if (object.thumbnail) { object.thumbnail = create(object.thumbnail, `thumbnail_${id}`) } if (object.src) { object.src = create(object.src, `src_${id}`) object.crossOrigin = 'anonymous' } if (object.fill?.source) { object.fill.source = create(object.fill.source, `fillsource_${id}`) } if (object.info?.fill?.source) { object.info.fill.source = create(object.info.fill.source, `infofillsource_${id}`) } }) return { canvas: JSON.stringify(canvas), images } } /** 处理JSON为正常画布 */ processCanvasDisUrlJSON(obj: { canvas: string, images: Object }) { var json = obj.canvas; const images = obj.images || {} for (const key in images) { json = json.replace(new RegExp(key), images[key]) } return json } dispose() { this.animationManager?.dispose() this.eventManager?.dispose() } }