diff --git a/src/hooks/useCanvasInteraction.ts b/src/hooks/useCanvasInteraction.ts index 0b0ab44..df56ab6 100644 --- a/src/hooks/useCanvasInteraction.ts +++ b/src/hooks/useCanvasInteraction.ts @@ -2,7 +2,13 @@ import { useEffect, useRef, useCallback } from 'react'; import type { RefObject } from 'react'; import { useAppStore } from '../store/useAppStore'; import { screenToImage, clamp, computeFitZoom, pointInRect } from '../lib/geometry'; -import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP, MIN_BOX_SIZE, HANDLE_HALFSIZE_PX } from '../lib/constants'; +import { + MIN_ZOOM, + MAX_ZOOM, + ZOOM_FACTOR, + MIN_BOX_SIZE, + HANDLE_HALFSIZE_PX, +} from '../lib/constants'; import type { DragHandle, DragState } from '../lib/types'; export interface DrawingState { @@ -200,8 +206,8 @@ export function useCanvasInteraction( if (e.ctrlKey || e.metaKey) { // Zoom toward cursor — must prevent browser zoom e.preventDefault(); - const delta = e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP; - applyZoom(zoom + delta, e.offsetX, e.offsetY); + const factor = e.deltaY < 0 ? ZOOM_FACTOR : 1 / ZOOM_FACTOR; + applyZoom(zoom * factor, e.offsetX, e.offsetY); } else if (e.shiftKey) { // Horizontal pan const d = e.deltaX || e.deltaY; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 54e5634..9e5b23c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -8,6 +8,6 @@ export const CLASSES: AnnotationClass[] = [ export const MIN_ZOOM = 0.25; export const MAX_ZOOM = 5.0; -export const ZOOM_STEP = 0.1; +export const ZOOM_FACTOR = 1.1; // multiplicative zoom instead of additive export const MIN_BOX_SIZE = 4; // px in image space, ignore smaller drags export const HANDLE_HALFSIZE_PX = 4; // half-size of resize handles in screen pixels (8px total) diff --git a/src/store/useAppStore.ts b/src/store/useAppStore.ts index 240454b..00d2e38 100644 --- a/src/store/useAppStore.ts +++ b/src/store/useAppStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; import type { Annotation, EditMode } from '../lib/types'; -import { CLASSES, MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from '../lib/constants'; +import { CLASSES, MIN_ZOOM, MAX_ZOOM, ZOOM_FACTOR } from '../lib/constants'; import { clamp, computeFitZoom } from '../lib/geometry'; interface AppState { @@ -175,7 +175,7 @@ export const useAppStore = create((set) => ({ canvasWidth && imageWidth ? computeFitZoom(canvasWidth, canvasHeight, imageWidth, imageHeight) : MIN_ZOOM; - const newZoom = clamp(zoom + ZOOM_STEP, minZoom, MAX_ZOOM); + const newZoom = clamp(zoom * ZOOM_FACTOR, minZoom, MAX_ZOOM); if (newZoom === zoom) return; const cx = canvasWidth / 2; const cy = canvasHeight / 2; @@ -191,7 +191,7 @@ export const useAppStore = create((set) => ({ canvasWidth && imageWidth ? computeFitZoom(canvasWidth, canvasHeight, imageWidth, imageHeight) : MIN_ZOOM; - const newZoom = clamp(zoom - ZOOM_STEP, minZoom, MAX_ZOOM); + const newZoom = clamp(zoom / ZOOM_FACTOR, minZoom, MAX_ZOOM); if (newZoom === zoom) return; const cx = canvasWidth / 2; const cy = canvasHeight / 2;