Files
aida_front/src/component/Canvas/CanvasEditor/managers/BrushIndicator.js

435 lines
10 KiB
JavaScript
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";
/**
* 笔刷指示器
* 在画笔模式下显示当前笔刷大小的圆圈指示器
* 使用独立的 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;
}
}