feat(canvas): 画布引擎 - 引入 LeaferJS 图形层并优化缩放清晰度与性能

This commit is contained in:
ddCat
2026-07-31 13:12:06 +08:00
parent a8b3727c38
commit 035d4bc185
14 changed files with 591 additions and 101 deletions

View File

@@ -0,0 +1,24 @@
import type { ViewportTransform } from "@/types/canvas";
const MIN_VIEWPORT_SCALE = 0.05;
const MIN_PREVIEW_RATIO = 0.85;
const MAX_PREVIEW_RATIO = 1.25;
export function calculateCanvasPreviewTransform(viewport: ViewportTransform, rasterViewport: ViewportTransform) {
const ratio = viewport.k / Math.max(rasterViewport.k, MIN_VIEWPORT_SCALE);
// 将旧 backing store 的屏幕坐标映射到实时视口,避免缩放手势逐帧触发矢量重绘。
return {
ratio,
x: viewport.x - rasterViewport.x * ratio,
y: viewport.y - rasterViewport.y * ratio,
};
}
export function shouldRebaseCanvasRaster(viewport: ViewportTransform, rasterViewport: ViewportTransform) {
const { ratio } = calculateCanvasPreviewTransform(viewport, rasterViewport);
return ratio < MIN_PREVIEW_RATIO || ratio > MAX_PREVIEW_RATIO;
}
export function sameCanvasViewport(left: ViewportTransform, right: ViewportTransform) {
return left.x === right.x && left.y === right.y && left.k === right.k;
}

View File

@@ -1,13 +1,16 @@
import type { ViewportTransform } from "@/types/canvas";
import type { SelectionBox, ViewportTransform } from "@/types/canvas";
export const CANVAS_VIEWPORT_PREVIEW_EVENT = "canvas:viewport-preview";
export const CANVAS_SELECTION_PREVIEW_EVENT = "canvas:selection-preview";
export function applyCanvasLiveViewport(container: HTMLDivElement | null, viewport: ViewportTransform, notify = true) {
if (!container) return;
const gridSize = 48 * viewport.k;
const committedScale = Number(container.style.getPropertyValue("--canvas-committed-scale")) || viewport.k;
container.style.setProperty("--canvas-live-x", `${viewport.x}px`);
container.style.setProperty("--canvas-live-y", `${viewport.y}px`);
container.style.setProperty("--canvas-live-scale", String(viewport.k));
container.style.setProperty("--canvas-live-scale-ratio", String(viewport.k / committedScale));
container.style.setProperty("--canvas-grid-size", `${gridSize}px`);
container.style.setProperty("--canvas-grid-x", `${viewport.x % gridSize}px`);
container.style.setProperty("--canvas-grid-y", `${viewport.y % gridSize}px`);
@@ -24,3 +27,13 @@ export function subscribeCanvasViewportPreview(container: HTMLDivElement, listen
container.addEventListener(CANVAS_VIEWPORT_PREVIEW_EVENT, handlePreview);
return () => container.removeEventListener(CANVAS_VIEWPORT_PREVIEW_EVENT, handlePreview);
}
export function applyCanvasSelectionPreview(container: HTMLDivElement | null, selection: SelectionBox) {
container?.dispatchEvent(new CustomEvent<SelectionBox>(CANVAS_SELECTION_PREVIEW_EVENT, { detail: selection }));
}
export function subscribeCanvasSelectionPreview(container: HTMLDivElement, listener: (selection: SelectionBox) => void) {
const handlePreview = (event: Event) => listener((event as CustomEvent<SelectionBox>).detail);
container.addEventListener(CANVAS_SELECTION_PREVIEW_EVENT, handlePreview);
return () => container.removeEventListener(CANVAS_SELECTION_PREVIEW_EVENT, handlePreview);
}