Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts

334 lines
9.1 KiB
TypeScript
Raw Normal View History

2026-03-09 13:44:32 +08:00
import { fabric } from 'fabric-with-all'
2026-03-19 11:00:31 +08:00
import { ref, computed } from 'vue'
2026-03-09 13:44:32 +08:00
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-17 10:53:43 +08:00
import { createId } from '../../tools/tools'
2026-03-09 13:44:32 +08:00
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)
}
})
2026-03-18 13:56:27 +08:00
const object = this.toObject_(arr)
if (object.info) {
let lock = !!object.info.lock
object.evented = !lock
object.selectable = !lock
}
return object
2026-03-13 11:18:36 +08:00
}
2026-03-09 13:44:32 +08:00
interface CanvasInitOptions {
canvasRef: any
canvasViewWidth?: number
canvasViewHeight?: number
canvasWidth?: number
canvasHeight?: number
2026-03-17 10:53:43 +08:00
url?: string
2026-03-09 13:44:32 +08:00
}
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
currentZoom: any
constructor(options) {
this.stateManager = options.stateManager;
this.deviceInfo = detectDeviceType();
this.currentZoom = ref(100)
}
2026-03-16 16:51:12 +08:00
onMounted() { }
2026-03-19 11:00:31 +08:00
getCanvasSize() {
return {
canvasViewWidth: this.canvas.width,
canvasViewHeight: this.canvas.height,
canvasWidth: this.canvas.clipPath.width,
canvasHeight: this.canvas.clipPath.height,
}
}
2026-03-09 13:44:32 +08:00
setCanvasViewSize(options) {
2026-03-19 11:00:31 +08:00
var canvasViewWidth = options.canvasViewWidth || 1920
var canvasViewHeight = options.canvasViewHeight || 1080
this.canvas.setWidth(canvasViewWidth)
this.canvas.setHeight(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-19 11:00:31 +08:00
var canvasWidth = options.canvasWidth || 750
var canvasHeight = options.canvasHeight || 600
2026-03-17 11:35:04 +08:00
var image = null;
2026-03-17 10:53:43 +08:00
if (options.url) {
2026-03-17 11:35:04 +08:00
await new Promise((resolve) => {
2026-03-17 10:53:43 +08:00
fabric.Image.fromURL(options.url, async (img) => {
2026-03-19 11:00:31 +08:00
canvasWidth = img.width
canvasHeight = img.height
2026-03-17 10:53:43 +08:00
img.set({
left: 0,
top: 0,
scaleX: 1,
scaleY: 1,
2026-03-18 13:56:27 +08:00
evented: false,
selectable: false,
2026-03-17 10:53:43 +08:00
info: {
id: createId("image"),
name: "图片图层",
2026-03-18 13:56:27 +08:00
lock: true,
2026-03-17 10:53:43 +08:00
}
})
2026-03-17 11:35:04 +08:00
image = img
2026-03-17 10:53:43 +08:00
resolve(img)
}, { crossOrigin: 'anonymous' })
})
}
2026-03-17 11:35:04 +08:00
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)
2026-03-09 13:44:32 +08:00
this.canvas.clipPath = new fabric.Rect({
left: 0,
top: 0,
2026-03-19 11:00:31 +08:00
width: canvasWidth,
height: canvasHeight
2026-03-09 13:44:32 +08:00
})
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, // 缩短默认动画时间
});
2026-03-17 10:53:43 +08:00
2026-03-12 11:40:48 +08:00
this.setupCanvasEvents()
this.setupBrushEvents()
2026-03-17 09:36:59 +08:00
/** 测试-开始 */
2026-03-19 11:00:31 +08:00
// 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)
2026-03-17 09:36:59 +08:00
/** 测试-结束 */
2026-03-17 10:53:43 +08:00
this.resetZoom(false, true)// 画布居中
this.layerManager.updateLayers()
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-16 16:51:12 +08:00
async add(obj: any, isRecord = 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-16 16:51:12 +08:00
if (id) {
2026-03-11 15:34:56 +08:00
this.layerManager.updateLayers()
this.renderAll()
2026-03-16 16:51:12 +08:00
await this.layerManager.updateLayerThumbnailsById(id)
2026-03-11 15:34:56 +08:00
}
2026-03-16 16:51:12 +08:00
if (isRecord) this.stateManager.recordState()
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)
2026-03-18 13:56:27 +08:00
if (obj && obj.evented) this.canvas.setActiveObject(obj)
2026-03-11 15:34:56 +08:00
this.renderAll()
}
2026-03-17 10:53:43 +08:00
resetZoom(animated = true, adaptive = true) {
this.animationManager.resetZoom(animated, adaptive)
2026-03-09 13:44:32 +08:00
}
2026-03-16 11:38:58 +08:00
// 使用动画管理器的缩放方法
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)
}
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
}
2026-03-18 13:56:27 +08:00
/** 获取选中对象 */
getSelectedObject() {
return this.canvas.getActiveObject()
2026-03-13 17:31:47 +08:00
}
2026-03-09 16:45:30 +08:00
renderAll() {
this.canvas.renderAll()
}
deleteObjectById(id: string) {
const object = this.getObjectById(id)
if (object) {
this.canvas.remove(object)
this.layerManager.updateLayers()
this.renderAll()
}
}
2026-03-18 13:56:27 +08:00
/** 取消选中对象 */
discardActiveObject() {
this.canvas.discardActiveObject()
this.renderAll()
}
2026-03-09 16:45:30 +08:00
// 拖拽排序
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 */
2026-03-19 13:17:03 +08:00
loadJSON(json: string, rerecord = true) {
2026-03-19 11:00:31 +08:00
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, () => {
2026-03-19 13:17:03 +08:00
if (rerecord) this.stateManager.clearState()
2026-03-19 11:00:31 +08:00
this.resetZoom(false)
this.layerManager.updateLayers()
this.renderAll()
2026-03-19 13:17:03 +08:00
if (rerecord) this.stateManager.recordState()
2026-03-19 11:00:31 +08:00
resolve(true)
})
2026-03-11 15:34:56 +08:00
})
}
2026-03-18 17:25:19 +08:00
/** 导出画布为处理图片的JSON */
getCanvasDisUrlJSON() {
const canvas = this.canvas.toJSON()
2026-03-20 09:46:15 +08:00
const images = {};
2026-03-18 17:25:19 +08:00
var i = 0;
const create = (url) => {
2026-03-20 09:46:15 +08:00
const key = `xxxxxxxx_${i++}_xxxxxxxx`;
images[key] = url
return key
2026-03-18 17:25:19 +08:00
}
canvas.objects.forEach((object: any) => {
if (object.thumbnail) {
object.thumbnail = create(object.thumbnail)
}
if (object.src) {
object.src = create(object.src)
2026-03-20 13:59:53 +08:00
object.crossOrigin = 'anonymous'
2026-03-18 17:25:19 +08:00
}
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为正常画布 */
2026-03-20 09:46:15 +08:00
processCanvasDisUrlJSON(obj: { canvas: string, images: Object }) {
2026-03-18 17:25:19 +08:00
var json = obj.canvas;
2026-03-20 09:46:15 +08:00
const images = obj.images || {}
for (const key in images) {
json = json.replace(new RegExp(key), images[key])
}
return json
2026-03-18 17:25:19 +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
}