From 5d9986f6c731346cba2333112e26644ab0901730 Mon Sep 17 00:00:00 2001 From: ddCat <2695092045@qq.com> Date: Sat, 22 Aug 2026 18:11:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(canvas):=20=E7=94=BB=E5=B8=83=E8=83=8C?= =?UTF-8?q?=E6=99=AF=20-=20=E9=99=8D=E4=BD=8E=E7=82=B9=E9=98=B5=E5=AF=86?= =?UTF-8?q?=E5=BA=A6=E4=B8=8E=E4=BA=AE=E5=BA=A6=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BC=A9=E6=94=BE=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/canvas/infinite-canvas.tsx | 13 ++++++++----- web/src/lib/canvas-theme.ts | 4 ++-- web/src/lib/canvas/canvas-live-viewport.ts | 14 +++++++++++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/web/src/components/canvas/infinite-canvas.tsx b/web/src/components/canvas/infinite-canvas.tsx index 23994a766..8ae9677e0 100644 --- a/web/src/components/canvas/infinite-canvas.tsx +++ b/web/src/components/canvas/infinite-canvas.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme"; -import { applyCanvasLiveViewport, canvasDotPx, subscribeCanvasViewportPreview } from "@/lib/canvas/canvas-live-viewport"; +import { applyCanvasLiveViewport, canvasDotGridPx, canvasDotPx, subscribeCanvasViewportPreview } from "@/lib/canvas/canvas-live-viewport"; import { useThemeStore } from "@/stores/use-theme-store"; import type { ViewportTransform } from "@/types/canvas"; @@ -382,6 +382,9 @@ export function InfiniteCanvas({ containerRef, viewport, backgroundMode = "lines "--canvas-grid-size": `${48 * viewport.k}px`, "--canvas-grid-x": `${viewport.x % (48 * viewport.k)}px`, "--canvas-grid-y": `${viewport.y % (48 * viewport.k)}px`, + "--canvas-dot-grid-size": `${canvasDotGridPx(viewport.k)}px`, + "--canvas-dot-grid-x": `${viewport.x % canvasDotGridPx(viewport.k)}px`, + "--canvas-dot-grid-y": `${viewport.y % canvasDotGridPx(viewport.k)}px`, "--canvas-dot-size": canvasDotPx(viewport.k), } as React.CSSProperties} onPointerDown={handlePointerDown} @@ -422,11 +425,11 @@ function CanvasGrid({ mode }: { mode: CanvasBackgroundMode }) { data-canvas-grid-layer className="pointer-events-none absolute" style={{ - inset: "calc(-1 * var(--canvas-grid-size))", + inset: mode === "dots" ? "calc(-1 * var(--canvas-dot-grid-size))" : "calc(-1 * var(--canvas-grid-size))", backgroundImage, - backgroundSize: "var(--canvas-grid-size) var(--canvas-grid-size)", - transform: "translate3d(var(--canvas-grid-x), var(--canvas-grid-y), 0)", - opacity: 0.46, + backgroundSize: mode === "dots" ? "var(--canvas-dot-grid-size) var(--canvas-dot-grid-size)" : "var(--canvas-grid-size) var(--canvas-grid-size)", + transform: mode === "dots" ? "translate3d(var(--canvas-dot-grid-x), var(--canvas-dot-grid-y), 0)" : "translate3d(var(--canvas-grid-x), var(--canvas-grid-y), 0)", + opacity: mode === "dots" ? 0.34 : 0.46, willChange: "transform", }} /> diff --git a/web/src/lib/canvas-theme.ts b/web/src/lib/canvas-theme.ts index 6b349a145..02f691f15 100644 --- a/web/src/lib/canvas-theme.ts +++ b/web/src/lib/canvas-theme.ts @@ -5,7 +5,7 @@ export const canvasThemes = { light: { canvas: { background: "#ffffff", - dot: "rgba(15,23,42,.30)", + dot: "rgba(15,23,42,.20)", line: "rgba(15,23,42,.15)", selectionFill: "rgba(17,17,17,.10)", }, @@ -71,7 +71,7 @@ export const canvasThemes = { dark: { canvas: { background: "#0b0b0b", - dot: "rgba(178,178,178,.27)", + dot: "rgba(178,178,178,.18)", line: "rgba(178,178,178,.14)", selectionFill: "rgba(255,255,255,.12)", }, diff --git a/web/src/lib/canvas/canvas-live-viewport.ts b/web/src/lib/canvas/canvas-live-viewport.ts index c3cab6203..596127de6 100644 --- a/web/src/lib/canvas/canvas-live-viewport.ts +++ b/web/src/lib/canvas/canvas-live-viewport.ts @@ -4,15 +4,20 @@ export const CANVAS_VIEWPORT_PREVIEW_EVENT = "canvas:viewport-preview"; export const CANVAS_GRAPHICS_VIEWPORT_PREVIEW_EVENT = "canvas:graphics-viewport-preview"; export const CANVAS_SELECTION_PREVIEW_EVENT = "canvas:selection-preview"; -// 空间网格点模式的点半径(像素单位)。远距(缩放 < 0.12)时用更小半径避免糊成一团。 -// 同时被 (infinite-canvas)与滚动同步(applyCanvasLiveViewport)共享,避免两处漂移。 +// 空间网格点模式的点半径(像素单位)。远距时使用更小半径,避免点阵糊成一团。 export function canvasDotPx(scale: number): string { - return scale < 0.12 ? "0.8px" : "1.25px"; + return scale < 0.12 ? "0.6px" : "0.8px"; +} + +// 点阵在缩小时不再无限压缩到屏幕像素,避免密集视觉噪声。 +export function canvasDotGridPx(scale: number): number { + return Math.max(48 * scale, 32); } export function applyCanvasLiveViewport(container: HTMLDivElement | null, viewport: ViewportTransform, notify = true) { if (!container) return; const gridSize = 48 * viewport.k; + const dotGridSize = canvasDotGridPx(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`); @@ -23,6 +28,9 @@ export function applyCanvasLiveViewport(container: HTMLDivElement | null, viewpo 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`); + container.style.setProperty("--canvas-dot-grid-size", `${dotGridSize}px`); + container.style.setProperty("--canvas-dot-grid-x", `${viewport.x % dotGridSize}px`); + container.style.setProperty("--canvas-dot-grid-y", `${viewport.y % dotGridSize}px`); container.style.setProperty("--canvas-dot-size", canvasDotPx(viewport.k)); // 图形层必须逐帧跟随 DOM 世界层;浮层和滚动通知仍可按原频率节流。 container.dispatchEvent(new CustomEvent(CANVAS_GRAPHICS_VIEWPORT_PREVIEW_EVENT, { detail: viewport }));