diff --git a/packages/frontend/eslint.config.js b/packages/frontend/eslint.config.js index 3b24a60..c7a7f2b 100644 --- a/packages/frontend/eslint.config.js +++ b/packages/frontend/eslint.config.js @@ -39,6 +39,7 @@ export default tseslint.config( "no-shadow": "off", "import/no-absolute-path": "warn", "import/no-unresolved": "warn", + "no-unused-vars": "off", "react-refresh/only-export-components": [ "warn", { allowConstantExport: true }, diff --git a/packages/frontend/src/hooks/useZoomSpace.ts b/packages/frontend/src/hooks/useZoomSpace.ts index 427bae4..ca1fe31 100644 --- a/packages/frontend/src/hooks/useZoomSpace.ts +++ b/packages/frontend/src/hooks/useZoomSpace.ts @@ -3,35 +3,10 @@ import React, { useRef } from "react"; import Konva from "konva"; import { KonvaEventObject } from "konva/lib/Node"; -const getMousePointTo = ( - stage: Konva.Stage, - pointer: { x: number; y: number }, - oldScale: number, -) => { - return { - x: (pointer.x - stage.x()) / oldScale, - y: (pointer.y - stage.y()) / oldScale, - }; -}; - -const calculateNewScale = ( - oldScale: number, - direction: number, - scaleBy: number, -) => { - return direction > 0 ? oldScale * scaleBy : oldScale / scaleBy; -}; - -const calculateNewPosition = ( - pointer: { x: number; y: number }, - mousePointTo: { x: number; y: number }, - newScale: number, -) => { - return { - x: pointer.x - mousePointTo.x * newScale, - y: pointer.y - mousePointTo.y * newScale, - }; -}; +import { + ctrlWheelZoomStrategy, + defaultMoveViewStrategy, +} from "@/utils/zoomStrategies"; interface UseZoomSpaceProps { stageRef: React.RefObject; @@ -48,68 +23,24 @@ export function useZoomSpace({ }: UseZoomSpaceProps) { const lastDistRef = useRef(null); - const moveView = (event: KonvaEventObject) => { - if (stageRef.current !== null) { - const stage = stageRef.current; - const currentScale = stage.scaleX(); - - stage.position({ - x: stage.x() - event.evt.deltaX / currentScale, - y: stage.y() - event.evt.deltaY / currentScale, - }); - } - }; - const zoomSpace = (event: KonvaEventObject) => { if (stageRef.current !== null) { const stage = stageRef.current; - // Ctrl + 휠로 확대/축소 if (event.evt.ctrlKey) { - event.evt.preventDefault(); - - const oldScale = stage.scaleX(); - const pointer = stage.getPointerPosition(); - - if (!pointer) return; - - const mousePointTo = getMousePointTo(stage, pointer, oldScale); - - const direction = - event.evt.deltaY > 0 - ? -1 // Ctrl + 휠: 아래로 휠 → 확대 - : 1; // Ctrl + 휠: 위로 휠 → 축소 - - let newScale = calculateNewScale(oldScale, direction, scaleBy); - - newScale = Math.max(minScale, Math.min(maxScale, newScale)); - - if (newScale === oldScale) { - return; - } - - const newPosition = calculateNewPosition( - pointer, - mousePointTo, - newScale, - ); - stage.scale({ x: newScale, y: newScale }); - stage.position(newPosition); + ctrlWheelZoomStrategy(event, stage, scaleBy, minScale, maxScale); } else { - // Ctrl 키가 없는 휠 이벤트는 화면 이동 처리 - moveView(event); + defaultMoveViewStrategy(event, stage); } } }; - // 핀치 줌 로직 const handleTouchMove = (event: KonvaEventObject) => { if (stageRef.current !== null && event.evt.touches.length === 2) { const stage = stageRef.current; const touch1 = event.evt.touches[0]; const touch2 = event.evt.touches[1]; - // 두 손가락 사이 거리 계산 const dist = Math.sqrt( (touch1.clientX - touch2.clientX) ** 2 + (touch1.clientY - touch2.clientY) ** 2, @@ -121,20 +52,20 @@ export function useZoomSpace({ if (!pointer) return; - const mousePointTo = getMousePointTo(stage, pointer, oldScale); + const mousePointTo = { + x: (pointer.x - stage.x()) / oldScale, + y: (pointer.y - stage.y()) / oldScale, + }; - // 확대/축소 비율 계산 const scaleChange = lastDistRef.current / dist; let newScale = oldScale * scaleChange; - newScale = Math.max(minScale, Math.min(maxScale, newScale)); if (newScale !== oldScale) { - const newPosition = calculateNewPosition( - pointer, - mousePointTo, - newScale, - ); + const newPosition = { + x: pointer.x - mousePointTo.x * newScale, + y: pointer.y - mousePointTo.y * newScale, + }; stage.scale({ x: newScale, y: newScale }); stage.position(newPosition); } diff --git a/packages/frontend/src/utils/zoomStrategies.ts b/packages/frontend/src/utils/zoomStrategies.ts new file mode 100644 index 0000000..ec86b36 --- /dev/null +++ b/packages/frontend/src/utils/zoomStrategies.ts @@ -0,0 +1,54 @@ +import Konva from "konva"; +import { KonvaEventObject } from "konva/lib/Node"; + +export type ZoomStrategy = ( + event: KonvaEventObject, + stage: Konva.Stage, + scaleBy: number, + minScale: number, + maxScale: number, +) => void; + +export const ctrlWheelZoomStrategy: ZoomStrategy = ( + event, + stage, + scaleBy, + minScale, + maxScale, +) => { + event.evt.preventDefault(); + + const oldScale = stage.scaleX(); + const pointer = stage.getPointerPosition(); + + if (!pointer) return; + + const mousePointTo = { + x: (pointer.x - stage.x()) / oldScale, + y: (pointer.y - stage.y()) / oldScale, + }; + + const direction = event.evt.deltaY > 0 ? -1 : 1; + let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy; + newScale = Math.max(minScale, Math.min(maxScale, newScale)); + + if (newScale === oldScale) return; + + const newPosition = { + x: pointer.x - mousePointTo.x * newScale, + y: pointer.y - mousePointTo.y * newScale, + }; + stage.scale({ x: newScale, y: newScale }); + stage.position(newPosition); +}; + +export const defaultMoveViewStrategy = ( + event: KonvaEventObject, + stage: Konva.Stage, +) => { + const currentScale = stage.scaleX(); + stage.position({ + x: stage.x() - event.evt.deltaX / currentScale, + y: stage.y() - event.evt.deltaY / currentScale, + }); +};