import { fabric } from "fabric-with-all"; /** * 笔刷指示器 * 在画笔模式下显示当前笔刷大小的圆圈指示器 * 使用独立的 fabric.StaticCanvas,完全不干扰主画布的绘制流程 */ export class BrushIndicator { /** * 构造函数 * @param {Object} canvas fabric.js画布实例 * @param {Object} options 配置选项 */ constructor(canvas, options = {}) { this.canvas = canvas; this.options = { strokeColor: options.strokeColor || "rgba(0, 0, 0, 0.5)", strokeWidth: options.strokeWidth || 1, fillColor: options.fillColor || "rgba(0, 0, 0, 0.1)", ...options, }; // 创建独立的静态画布 this.indicatorCanvas = null; this.staticCanvas = null; this.indicatorCircle = null; this._createStaticCanvas(); // 事件处理器 this._mouseEnterHandler = null; this._mouseLeaveHandler = null; this._mouseMoveHandler = null; this._zoomHandler = null; this._viewportHandler = null; // 当前状态 this.isVisible = false; this.currentSize = 10; this.isEnabled = false; this.currentPosition = { x: 0, y: 0 }; } /** * 创建独立的 fabric.StaticCanvas 画布层 * @private */ _createStaticCanvas() { if (!this.canvas.wrapperEl) return; // 创建独立的 canvas 元素 this.indicatorCanvas = document.createElement("canvas"); this.indicatorCanvas.className = "brush-indicator-canvas"; // 设置样式,使其覆盖在主画布上 Object.assign(this.indicatorCanvas.style, { position: "absolute", top: "0", left: "0", pointerEvents: "none", // 不阻挡鼠标事件 zIndex: "10", // 确保在最上层 width: "100%", height: "100%", }); // 将指示器画布添加到 fabric.js 画布容器中 this.canvas.wrapperEl.appendChild(this.indicatorCanvas); // 创建 fabric.StaticCanvas 实例 this.staticCanvas = new fabric.StaticCanvas(this.indicatorCanvas, { enableRetinaScaling: this.canvas.enableRetinaScaling, allowTouchScrolling: false, selection: false, skipTargetFind: true, preserveObjectStacking: true, }); // 同步画布尺寸和变换 this._syncCanvasProperties(); // 监听主画布变化 this._observeCanvasChanges(); } /** * 同步画布属性(尺寸、缩放、视口变换等) * @private */ _syncCanvasProperties() { if (!this.staticCanvas || !this.canvas) return; // 同步画布尺寸 this.staticCanvas.setWidth(this.canvas.width); this.staticCanvas.setHeight(this.canvas.height); // 同步缩放 this.staticCanvas.setZoom(this.canvas.getZoom()); // 同步视口变换 const vpt = this.canvas.viewportTransform; if (vpt) { this.staticCanvas.setViewportTransform([...vpt]); } // 同步背景色(可选,通常保持透明) // this.staticCanvas.backgroundColor = 'transparent'; // 重新渲染 this.staticCanvas.renderAll(); } /** * 监听主画布变化 * @private */ _observeCanvasChanges() { if (!this.canvas) return; // 监听缩放变化 this._zoomHandler = () => { this._syncCanvasProperties(); }; // 监听视口变化 this._viewportHandler = () => { this._syncCanvasProperties(); }; // 监听画布尺寸变化 this._resizeHandler = () => { this._syncCanvasProperties(); }; // 绑定事件 this.canvas.on("after:render", this._zoomHandler); this.canvas.on("canvas:zoomed", this._zoomHandler); this.canvas.on("viewport:changed", this._viewportHandler); this.canvas.on("canvas:resized", this._resizeHandler); // 使用 ResizeObserver 监听 DOM 尺寸变化 if (window.ResizeObserver && this.canvas.upperCanvasEl) { this.resizeObserver = new ResizeObserver(() => { this._syncCanvasProperties(); }); this.resizeObserver.observe(this.canvas.upperCanvasEl); } } /** * 启用笔刷指示器 * @param {Number} brushSize 笔刷大小 */ enable(brushSize) { if (this.isEnabled) return; this.isEnabled = true; this.currentSize = brushSize; // 绑定事件 this._bindEvents(); } /** * 禁用笔刷指示器 */ disable() { if (!this.isEnabled) return; this.isEnabled = false; // 隐藏指示器 this.hide(); // 重置颜色配置为默认值 this.options.strokeColor = ""; this.options.fillColor = ""; // 解绑事件 this._unbindEvents(); } /** * 更新笔刷大小 * @param {Number} size 新的笔刷大小 */ updateSize(size) { this.currentSize = size; // 如果指示器正在显示,更新其大小 if (this.isVisible && this.indicatorCircle) { this.indicatorCircle.set({ radius: size / 2, }); this.staticCanvas.renderAll(); } } /** * 更新指示器颜色 * @param {String} color 新的颜色值 */ updateColor(color) { // 更新配置选项中的颜色 if (color) { this.options.strokeColor = color; } // 如果指示器正在显示,更新其颜色 if (this.isVisible && this.indicatorCircle) { this.indicatorCircle.set({ stroke: this.options.strokeColor, fill: this.options.fillColor, }); this.staticCanvas.renderAll(); } } /** * 显示指示器 * @param {Object} pointer 鼠标位置 */ show(pointer) { if (!this.isEnabled || this.isVisible) return; this.isVisible = true; // 创建指示器圆圈 this._createIndicatorCircle(); // 更新位置 this.updatePosition(pointer); } /** * 隐藏指示器 */ hide() { if (!this.isVisible) return; this.isVisible = false; // 移除指示器圆圈 if (this.indicatorCircle && this.staticCanvas) { this.staticCanvas.remove(this.indicatorCircle); this.indicatorCircle = null; this.staticCanvas.renderAll(); } } /** * 更新指示器位置 * @param {Object} pointer 鼠标位置 */ updatePosition(pointer) { if (!this.isVisible || !this.indicatorCircle) return; // 使用主画布的坐标转换 const canvasPointer = this.canvas.getPointer(pointer); // 更新当前位置 this.currentPosition = { x: canvasPointer.x, y: canvasPointer.y, }; // 更新指示器位置 this.indicatorCircle.set({ left: canvasPointer.x, top: canvasPointer.y, }); // 重新渲染静态画布 // this.staticCanvas.renderAll(); // 优化渲染 this.staticCanvas.requestRenderAll(); } /** * 创建指示器圆圈对象 * @private */ _createIndicatorCircle() { if (this.indicatorCircle || !this.staticCanvas) return; // 创建圆圈对象 this.indicatorCircle = new fabric.Circle({ radius: this.currentSize / 2, fill: this.options.fillColor, stroke: this.options.strokeColor, strokeWidth: this.options.strokeWidth, originX: "center", originY: "center", selectable: false, evented: false, excludeFromExport: true, isTemp: true, pointer: true, }); // 添加到静态画布 this.staticCanvas.add(this.indicatorCircle); } /** * 绑定事件处理器 * @private */ _bindEvents() { if (!this.canvas) return; // 鼠标进入画布 this._mouseEnterHandler = (e) => { if (this._shouldShowIndicator()) { this.show(e.e); } }; // 鼠标离开画布 this._mouseLeaveHandler = () => { this.hide(); }; // 鼠标移动 this._mouseMoveHandler = (e) => { if (this._shouldShowIndicator()) { if (!this.isVisible) { this.show(e.e); } else { // requestIdleCallback(() => { // this.updatePosition(e.e); // }); requestAnimationFrame(() => { this.updatePosition(e.e); }); } } else { this.hide(); } }; // 绑定事件 this.canvas.on("mouse:over", this._mouseEnterHandler); this.canvas.on("mouse:out", this._mouseLeaveHandler); this.canvas.on("mouse:move", this._mouseMoveHandler); } /** * 解绑事件处理器 * @private */ _unbindEvents() { if (!this.canvas) return; // 解绑鼠标事件 if (this._mouseEnterHandler) { this.canvas.off("mouse:over", this._mouseEnterHandler); this._mouseEnterHandler = null; } if (this._mouseLeaveHandler) { this.canvas.off("mouse:out", this._mouseLeaveHandler); this._mouseLeaveHandler = null; } if (this._mouseMoveHandler) { this.canvas.off("mouse:move", this._mouseMoveHandler); this._mouseMoveHandler = null; } // 解绑画布变化事件 if (this._zoomHandler) { this.canvas.off("after:render", this._zoomHandler); this.canvas.off("canvas:zoomed", this._zoomHandler); this._zoomHandler = null; } if (this._viewportHandler) { this.canvas.off("viewport:changed", this._viewportHandler); this._viewportHandler = null; } if (this._resizeHandler) { this.canvas.off("canvas:resized", this._resizeHandler); this._resizeHandler = null; } } /** * 判断是否应该显示指示器 * @private * @returns {Boolean} 是否显示 */ _shouldShowIndicator() { // 检查画布是否在绘图模式 if (!this.canvas.isDrawingMode) return false; // 检查是否有笔刷 if (!this.canvas.freeDrawingBrush) return false; return true; } /** * 销毁指示器 */ dispose() { this.disable(); // 解绑画布变化事件 this._unbindEvents(); // 停止监听尺寸变化 if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } // 销毁静态画布 if (this.staticCanvas) { this.staticCanvas.dispose(); this.staticCanvas = null; } // 移除指示器画布 if (this.indicatorCanvas && this.indicatorCanvas.parentNode) { this.indicatorCanvas.parentNode.removeChild(this.indicatorCanvas); } this.canvas = null; this.indicatorCanvas = null; this.indicatorCircle = null; this.options = null; } }