Skip to content

Commit a441a61

Browse files
committed
refactor: 줌 기능에 strategy 패턴 적용
1 parent 549be86 commit a441a61

File tree

4 files changed

+139
-1
lines changed

4 files changed

+139
-1
lines changed

packages/frontend/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export default tseslint.config(
3939
"no-shadow": "off",
4040
"import/no-absolute-path": "warn",
4141
"import/no-unresolved": "warn",
42+
"no-unused-vars": "off",
4243
"react-refresh/only-export-components": [
4344
"warn",
4445
{ allowConstantExport: true },

packages/frontend/src/components/space/SpaceView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import useDragNode from "@/hooks/useDragNode";
1616
import useMoveNode from "@/hooks/useMoveNode";
1717
import useSpaceSelection from "@/hooks/useSpaceSelection";
1818
import useYjsSpace from "@/hooks/useYjsSpace";
19-
import { useZoomSpace } from "@/hooks/useZoomSpace";
19+
import { useZoomSpace } from "@/hooks/useZoomSpaceNew";
2020

2121
import PointerLayer from "../PointerLayer";
2222
import GooeyNode from "./GooeyNode";
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React, { useRef } from "react";
2+
3+
import Konva from "konva";
4+
import { KonvaEventObject } from "konva/lib/Node";
5+
6+
import {
7+
ctrlWheelZoomStrategy,
8+
defaultMoveViewStrategy,
9+
} from "@/utils/zoomStrategies";
10+
11+
interface UseZoomSpaceProps {
12+
stageRef: React.RefObject<Konva.Stage>;
13+
scaleBy?: number;
14+
minScale?: number;
15+
maxScale?: number;
16+
}
17+
18+
export function useZoomSpace({
19+
stageRef,
20+
scaleBy = 1.018,
21+
minScale = 0.5,
22+
maxScale = 3,
23+
}: UseZoomSpaceProps) {
24+
const lastDistRef = useRef<number | null>(null);
25+
26+
const zoomSpace = (event: KonvaEventObject<WheelEvent>) => {
27+
if (stageRef.current !== null) {
28+
const stage = stageRef.current;
29+
30+
if (event.evt.ctrlKey) {
31+
ctrlWheelZoomStrategy(event, stage, scaleBy, minScale, maxScale);
32+
} else {
33+
defaultMoveViewStrategy(event, stage);
34+
}
35+
}
36+
};
37+
38+
const handleTouchMove = (event: KonvaEventObject<TouchEvent>) => {
39+
if (stageRef.current !== null && event.evt.touches.length === 2) {
40+
const stage = stageRef.current;
41+
const touch1 = event.evt.touches[0];
42+
const touch2 = event.evt.touches[1];
43+
44+
const dist = Math.sqrt(
45+
(touch1.clientX - touch2.clientX) ** 2 +
46+
(touch1.clientY - touch2.clientY) ** 2,
47+
);
48+
49+
if (lastDistRef.current !== null) {
50+
const oldScale = stage.scaleX();
51+
const pointer = stage.getPointerPosition();
52+
53+
if (!pointer) return;
54+
55+
const mousePointTo = {
56+
x: (pointer.x - stage.x()) / oldScale,
57+
y: (pointer.y - stage.y()) / oldScale,
58+
};
59+
60+
const scaleChange = lastDistRef.current / dist;
61+
let newScale = oldScale * scaleChange;
62+
newScale = Math.max(minScale, Math.min(maxScale, newScale));
63+
64+
if (newScale !== oldScale) {
65+
const newPosition = {
66+
x: pointer.x - mousePointTo.x * newScale,
67+
y: pointer.y - mousePointTo.y * newScale,
68+
};
69+
stage.scale({ x: newScale, y: newScale });
70+
stage.position(newPosition);
71+
}
72+
}
73+
74+
lastDistRef.current = dist;
75+
}
76+
};
77+
78+
const handleTouchEnd = () => {
79+
lastDistRef.current = null;
80+
};
81+
82+
return { zoomSpace, handleTouchMove, handleTouchEnd };
83+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Konva from "konva";
2+
import { KonvaEventObject } from "konva/lib/Node";
3+
4+
export type ZoomStrategy = (
5+
event: KonvaEventObject<WheelEvent>,
6+
stage: Konva.Stage,
7+
scaleBy: number,
8+
minScale: number,
9+
maxScale: number,
10+
) => void;
11+
12+
export const ctrlWheelZoomStrategy: ZoomStrategy = (
13+
event,
14+
stage,
15+
scaleBy,
16+
minScale,
17+
maxScale,
18+
) => {
19+
event.evt.preventDefault();
20+
21+
const oldScale = stage.scaleX();
22+
const pointer = stage.getPointerPosition();
23+
24+
if (!pointer) return;
25+
26+
const mousePointTo = {
27+
x: (pointer.x - stage.x()) / oldScale,
28+
y: (pointer.y - stage.y()) / oldScale,
29+
};
30+
31+
const direction = event.evt.deltaY > 0 ? -1 : 1;
32+
let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy;
33+
newScale = Math.max(minScale, Math.min(maxScale, newScale));
34+
35+
if (newScale === oldScale) return;
36+
37+
const newPosition = {
38+
x: pointer.x - mousePointTo.x * newScale,
39+
y: pointer.y - mousePointTo.y * newScale,
40+
};
41+
stage.scale({ x: newScale, y: newScale });
42+
stage.position(newPosition);
43+
};
44+
45+
export const defaultMoveViewStrategy = (
46+
event: KonvaEventObject<WheelEvent>,
47+
stage: Konva.Stage,
48+
) => {
49+
const currentScale = stage.scaleX();
50+
stage.position({
51+
x: stage.x() - event.evt.deltaX / currentScale,
52+
y: stage.y() - event.evt.deltaY / currentScale,
53+
});
54+
};

0 commit comments

Comments
 (0)