feat: 优化画布缩放交互 - 滚轮直接缩放,添加缩放控制按钮
This commit is contained in:
parent
ff5e78c5a9
commit
be6f22b3d6
@ -9,6 +9,8 @@ import { ImageNode } from "./nodes/ImageNode";
|
||||
import { TextNode } from "./nodes/TextNode";
|
||||
import { VideoNode } from "./nodes/VideoNode";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Minus, Plus } from "lucide-react";
|
||||
|
||||
// 性能优化:判断节点是否在视口内
|
||||
function isNodeInViewport(
|
||||
@ -112,29 +114,31 @@ export function CanvasWorkspace() {
|
||||
|
||||
// 处理滚轮缩放
|
||||
const handleWheel = useCallback((e: React.WheelEvent) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
||||
const newZoom = Math.max(0.1, Math.min(4, viewport.zoom * delta));
|
||||
|
||||
// 以鼠标位置为中心缩放
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
if (rect) {
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
const scaleChange = newZoom / viewport.zoom;
|
||||
const newX = mouseX - (mouseX - viewport.x) * scaleChange;
|
||||
const newY = mouseY - (mouseY - viewport.y) * scaleChange;
|
||||
|
||||
setViewport({ x: newX, y: newY, zoom: newZoom });
|
||||
}
|
||||
} else {
|
||||
// 普通滚动平移
|
||||
e.preventDefault();
|
||||
|
||||
// Shift + 滚轮 = 平移
|
||||
if (e.shiftKey) {
|
||||
setViewport({
|
||||
x: viewport.x - e.deltaX,
|
||||
y: viewport.y - e.deltaY,
|
||||
x: viewport.x - e.deltaY,
|
||||
y: viewport.y - e.deltaX,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认滚轮 = 缩放(以鼠标位置为中心)
|
||||
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
||||
const newZoom = Math.max(0.1, Math.min(4, viewport.zoom * delta));
|
||||
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
if (rect) {
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
const scaleChange = newZoom / viewport.zoom;
|
||||
const newX = mouseX - (mouseX - viewport.x) * scaleChange;
|
||||
const newY = mouseY - (mouseY - viewport.y) * scaleChange;
|
||||
|
||||
setViewport({ x: newX, y: newY, zoom: newZoom });
|
||||
}
|
||||
}, [viewport, setViewport]);
|
||||
|
||||
@ -267,9 +271,41 @@ export function CanvasWorkspace() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 缩放指示器 */}
|
||||
<div className="absolute bottom-4 right-4 bg-background/80 backdrop-blur-sm rounded-md px-3 py-1.5 text-xs text-muted-foreground">
|
||||
{Math.round(viewport.zoom * 100)}%
|
||||
{/* 缩放控制器 */}
|
||||
<div className="absolute bottom-4 right-4 bg-background/80 backdrop-blur-sm rounded-lg flex items-center gap-1 px-1 py-1 shadow-sm border border-border/50">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => {
|
||||
const newZoom = Math.max(0.1, viewport.zoom - 0.1);
|
||||
setViewport({ zoom: newZoom });
|
||||
}}
|
||||
title="缩小"
|
||||
>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 min-w-[50px] text-xs font-medium"
|
||||
onClick={() => setViewport({ zoom: 1, x: 0, y: 0 })}
|
||||
title="重置缩放"
|
||||
>
|
||||
{Math.round(viewport.zoom * 100)}%
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => {
|
||||
const newZoom = Math.min(4, viewport.zoom + 0.1);
|
||||
setViewport({ zoom: newZoom });
|
||||
}}
|
||||
title="放大"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user