Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts
2026-03-20 13:23:00 +08:00

344 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { createId } from '../../tools/tools'
// 自定义画布转对象属性
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)
}
})
const object = this.toObject_(arr)
if (object.info) {
let lock = !!object.info.lock
object.evented = !lock
object.selectable = !lock
}
return object
}
fabric.Image.fromURL = (function (originalFromURL) {
return function (url, callback, imgOptions) {
// 为所有图片请求添加 crossOrigin
const options = {
crossOrigin: 'anonymous', // 关键设置
...imgOptions
};
return originalFromURL.call(this, url, callback, options);
};
})(fabric.Image.fromURL);
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
constructor(options) {
this.stateManager = options.stateManager;
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 rect = await this.layerManager.createRectLayer({ left: 200 })
// await this.layerManager.createStarLayer({ left: 400 })
// await this.layerManager.createArrowLayer({ left: 600 })
// this.layerManager.setActiveID(rect.info.id)
// this.stateManager.setIsRecord(true)
/** 测试-结束 */
this.resetZoom(false, true)// 画布居中
this.layerManager.updateLayers()
this.stateManager.recordState()
// this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
}
/** 画布添加对象 */
async add(obj: any, isRecord = true) {
this.canvas.add(obj)
const id = obj?.info?.id || ""
if (id) {
this.layerManager.updateLayers()
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()
}
}
/** 设置画布事件 */
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) {
const obj = this.getObjectById(id)
if (obj && 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) {
const object = this.getObjectById(id)
if (object) {
this.canvas.remove(object)
this.layerManager.updateLayers()
this.renderAll()
}
}
/** 取消选中对象 */
discardActiveObject() {
this.canvas.discardActiveObject()
this.renderAll()
}
// 拖拽排序
dragSort(id, newIndex) {
this.canvas.moveTo(this.getObjectById(id), newIndex)
this.layerManager.updateLayers()
this.renderAll()
this.stateManager.recordState()
}
/** 画笔事件 */
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 {
const emptyLayer = 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)
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 = {};
var i = 0;
const create = (url) => {
const key = `xxxxxxxx_${i++}_xxxxxxxx`;
images[key] = url
return key
}
canvas.objects.forEach((object: any) => {
if (object.thumbnail) {
object.thumbnail = create(object.thumbnail)
}
if (object.src) {
object.src = create(object.src)
}
if (object.fill?.source) {
object.fill.source = create(object.fill.source)
}
if (object.info?.fill?.source) {
object.info.fill.source = create(object.info.fill.source)
}
})
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()
}
}