Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/frontend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
97 changes: 14 additions & 83 deletions packages/frontend/src/hooks/useZoomSpace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Konva.Stage>;
Expand All @@ -48,68 +23,24 @@ export function useZoomSpace({
}: UseZoomSpaceProps) {
const lastDistRef = useRef<number | null>(null);

const moveView = (event: KonvaEventObject<WheelEvent>) => {
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<WheelEvent>) => {
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<TouchEvent>) => {
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,
Expand All @@ -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);
}
Expand Down
54 changes: 54 additions & 0 deletions packages/frontend/src/utils/zoomStrategies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Konva from "konva";
import { KonvaEventObject } from "konva/lib/Node";

export type ZoomStrategy = (
event: KonvaEventObject<WheelEvent>,
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<WheelEvent>,
stage: Konva.Stage,
) => {
const currentScale = stage.scaleX();
stage.position({
x: stage.x() - event.evt.deltaX / currentScale,
y: stage.y() - event.evt.deltaY / currentScale,
});
};