297 lines
8.0 KiB
TypeScript
297 lines
8.0 KiB
TypeScript
import { fabric } from 'fabric-with-all'
|
||
import { ref } 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
|
||
}
|
||
|
||
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
|
||
canvasViewWidth: number
|
||
canvasViewHeight: number
|
||
canvasWidth: number
|
||
canvasHeight: number
|
||
currentZoom: any
|
||
constructor(options) {
|
||
this.stateManager = options.stateManager;
|
||
this.deviceInfo = detectDeviceType();
|
||
this.currentZoom = ref(100)
|
||
}
|
||
onMounted() { }
|
||
setCanvasViewSize(options) {
|
||
this.canvasViewWidth = options.canvasViewWidth || 1920
|
||
this.canvasViewHeight = options.canvasViewHeight || 1080
|
||
this.canvas.setWidth(this.canvasViewWidth)
|
||
this.canvas.setHeight(this.canvasViewHeight)
|
||
}
|
||
/** 初始化画布 */
|
||
async initCanvas(options: CanvasInitOptions) {
|
||
this.layerManager = this.stateManager.layerManager
|
||
this.canvasWidth = options.canvasWidth || 750
|
||
this.canvasHeight = options.canvasHeight || 600
|
||
var image = null;
|
||
if (options.url) {
|
||
await new Promise((resolve) => {
|
||
fabric.Image.fromURL(options.url, async (img) => {
|
||
this.canvasWidth = img.width
|
||
this.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: this.canvasWidth,
|
||
height: this.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: 400,
|
||
// top: 100,
|
||
// })
|
||
// //创建圆形
|
||
// const circle = await this.layerManager.createCircleLayer({
|
||
// left: 200,
|
||
// top: 200,
|
||
// })
|
||
// // 文字
|
||
// const text = await this.layerManager.createTextLayer('Hello World');
|
||
// this.layerManager.setActiveID(text.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, 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)
|
||
})
|
||
}
|
||
dispose() {
|
||
this.animationManager?.dispose()
|
||
this.eventManager?.dispose()
|
||
}
|
||
}
|