@deepnoid/canvas 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AnnotatedCanvas.d.ts +3 -2
- package/dist/components/AnnotatedCanvas.js +65 -17
- package/dist/hooks/usePanZoom.d.ts +21 -14
- package/dist/hooks/usePanZoom.js +104 -118
- package/dist/hooks/useResizeObserver.js +3 -3
- package/dist/utils/graphic.d.ts +5 -0
- package/dist/utils/graphic.js +16 -0
- package/package.json +1 -1
- package/dist/hooks/useImageLoader.d.ts +0 -6
- package/dist/hooks/useImageLoader.js +0 -71
|
@@ -14,8 +14,9 @@ type Props = {
|
|
|
14
14
|
}>;
|
|
15
15
|
resetOnImageChange?: boolean;
|
|
16
16
|
editable?: boolean;
|
|
17
|
-
|
|
17
|
+
timeout?: number;
|
|
18
18
|
onImageLoadSuccess?: () => void;
|
|
19
|
+
onImageLoadError?: (error: Error) => void;
|
|
19
20
|
};
|
|
20
|
-
declare const AnnotatedCanvas: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable,
|
|
21
|
+
declare const AnnotatedCanvas: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable, timeout, onImageLoadSuccess, onImageLoadError, }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
21
22
|
export default AnnotatedCanvas;
|
|
@@ -1,32 +1,80 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import Canvas from './Canvas';
|
|
5
|
-
import { useImageLoader } from '../hooks/useImageLoader';
|
|
6
5
|
import { usePanZoom } from '../hooks/usePanZoom';
|
|
7
|
-
|
|
6
|
+
import useResizeObserver from '../hooks/useResizeObserver';
|
|
7
|
+
import { drawCanvas, resolutionCanvas } from '../utils/graphic';
|
|
8
|
+
const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, timeout = 10000, onImageLoadSuccess, onImageLoadError, }) => {
|
|
8
9
|
const canvasRef = useRef(null);
|
|
9
10
|
const imageRef = useRef(new Image());
|
|
11
|
+
const { width, height } = useResizeObserver({ ref: canvasRef });
|
|
10
12
|
const [displayCoordinates, setDisplayCoordinates] = useState();
|
|
11
|
-
const {
|
|
12
|
-
const { moveX, moveY, zoom, initZoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, initCanvas, } = usePanZoom({
|
|
13
|
-
canvasRef,
|
|
14
|
-
imageRef,
|
|
15
|
-
panZoomable,
|
|
16
|
-
resetOnImageChange,
|
|
17
|
-
});
|
|
18
|
-
const loaded = loadedImage && !isLoading;
|
|
13
|
+
const { canvasState, init, initCanvas, clearCanvas, preserveZoomAndPosition, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, imageOnloadCount, setImageOnloadCount, } = usePanZoom({ canvasRef, imageRef, panZoomable });
|
|
19
14
|
useEffect(() => {
|
|
20
|
-
if (
|
|
15
|
+
if (!image || image.trim() === '') {
|
|
16
|
+
clearCanvas();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
imageRef.current.src = image;
|
|
20
|
+
imageRef.current.onload = () => {
|
|
21
|
+
onImageLoadSuccess?.();
|
|
22
|
+
const canvas = resolutionCanvas(canvasRef.current);
|
|
23
|
+
if (!canvas)
|
|
24
|
+
return;
|
|
25
|
+
if (resetOnImageChange) {
|
|
26
|
+
init(canvas, imageRef.current);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
preserveZoomAndPosition(canvas, imageRef.current, canvasState.zoom / canvasState.initZoom);
|
|
30
|
+
}
|
|
31
|
+
setImageOnloadCount((prev) => prev + 1);
|
|
21
32
|
setDisplayCoordinates(coordinates);
|
|
33
|
+
};
|
|
34
|
+
imageRef.current.onerror = () => {
|
|
35
|
+
const err = new Error(`Failed to load image: ${image}`);
|
|
36
|
+
onImageLoadError?.(err);
|
|
37
|
+
clearCanvas();
|
|
38
|
+
setDisplayCoordinates([]);
|
|
39
|
+
};
|
|
40
|
+
}, [image, resetOnImageChange]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (image && width && height) {
|
|
43
|
+
const canvas = resolutionCanvas(canvasRef.current);
|
|
44
|
+
if (canvas)
|
|
45
|
+
init(canvas, imageRef.current);
|
|
22
46
|
}
|
|
23
|
-
}, [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
47
|
+
}, [width, height]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!canvasRef.current)
|
|
50
|
+
return;
|
|
51
|
+
const redraw = (canvas) => {
|
|
52
|
+
drawCanvas(canvasState.moveX, canvasState.moveY, canvasState.zoomX, canvasState.zoomY, canvasState.zoom, canvasState.dx, canvasState.dy, canvas, imageRef.current, true);
|
|
53
|
+
};
|
|
54
|
+
const canvas = resolutionCanvas(canvasRef.current);
|
|
55
|
+
if (canvas)
|
|
56
|
+
redraw(canvas);
|
|
57
|
+
}, [canvasState, imageOnloadCount]);
|
|
58
|
+
return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children: _jsxs("div", { onWheel: handleWheel, onMouseMove: handleMouseMove, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, style: {
|
|
27
59
|
flex: 1,
|
|
28
60
|
position: 'relative',
|
|
29
61
|
cursor: panZoomable ? 'grab' : 'default',
|
|
30
|
-
}, children:
|
|
62
|
+
}, children: [_jsx("canvas", { ref: canvasRef, style: {
|
|
63
|
+
position: 'absolute',
|
|
64
|
+
width: '100%',
|
|
65
|
+
height: '100%',
|
|
66
|
+
left: 0,
|
|
67
|
+
top: 0,
|
|
68
|
+
} }), _jsx(Canvas, { ...canvasState,
|
|
69
|
+
// moveX={canvasState.moveX}
|
|
70
|
+
// moveY={canvasState.moveY}
|
|
71
|
+
// zoomX={canvasState.zoomX}
|
|
72
|
+
// zoomY={canvasState.zoomY}
|
|
73
|
+
// zoom={canvasState.zoom}
|
|
74
|
+
// dx={canvasState.dx}
|
|
75
|
+
// dy={canvasState.dy}
|
|
76
|
+
// dw={canvasState.dw}
|
|
77
|
+
// dh={canvasState.dh}
|
|
78
|
+
coordinates: displayCoordinates, editable: editable }), ZoomButton && canvasState.initZoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((canvasState.zoom / canvasState.initZoom) * 100)}%` }))] }) }));
|
|
31
79
|
};
|
|
32
80
|
export default AnnotatedCanvas;
|
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
import { RefObject, WheelEvent, MouseEvent } from 'react';
|
|
2
|
-
type
|
|
3
|
-
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
4
|
-
imageRef: RefObject<HTMLImageElement | null>;
|
|
5
|
-
panZoomable?: boolean;
|
|
6
|
-
resetOnImageChange?: boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare const usePanZoom: ({ canvasRef, imageRef, panZoomable, resetOnImageChange, }: UsePanZoomProps) => {
|
|
9
|
-
moveX: number;
|
|
10
|
-
moveY: number;
|
|
11
|
-
zoom: number;
|
|
12
|
-
initZoom: number;
|
|
13
|
-
zoomX: number;
|
|
14
|
-
zoomY: number;
|
|
2
|
+
type CanvasState = {
|
|
15
3
|
dx: number;
|
|
16
4
|
dy: number;
|
|
17
5
|
dw: number;
|
|
18
6
|
dh: number;
|
|
7
|
+
moveX: number;
|
|
8
|
+
moveY: number;
|
|
9
|
+
zoomX: number;
|
|
10
|
+
zoomY: number;
|
|
11
|
+
zoom: number;
|
|
12
|
+
initZoom: number;
|
|
13
|
+
};
|
|
14
|
+
type UsePanZoomProps = {
|
|
15
|
+
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
16
|
+
imageRef: RefObject<HTMLImageElement>;
|
|
17
|
+
panZoomable?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function usePanZoom({ canvasRef, imageRef, panZoomable }: UsePanZoomProps): {
|
|
20
|
+
canvasState: CanvasState;
|
|
21
|
+
imageOnloadCount: number;
|
|
22
|
+
setImageOnloadCount: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
23
|
+
init: (canvas: HTMLCanvasElement, image: HTMLImageElement) => void;
|
|
24
|
+
initCanvas: () => void;
|
|
25
|
+
clearCanvas: () => void;
|
|
26
|
+
preserveZoomAndPosition: (canvas: HTMLCanvasElement, image: HTMLImageElement, zoomRatio: number) => void;
|
|
19
27
|
handleWheel: (event: WheelEvent) => void;
|
|
20
28
|
handleMouseDown: (event: MouseEvent) => void;
|
|
21
29
|
handleMouseMove: (event: MouseEvent) => void;
|
|
22
30
|
handleMouseUp: (event: MouseEvent) => void;
|
|
23
31
|
handleMouseLeave: (event: MouseEvent) => void;
|
|
24
|
-
initCanvas: () => void;
|
|
25
32
|
};
|
|
26
33
|
export {};
|
package/dist/hooks/usePanZoom.js
CHANGED
|
@@ -1,169 +1,155 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import { resolutionCanvas,
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { resolutionCanvas, getMousePointTransform, canvasCenterPoint, calculatorZoomPoint, calculateInitZoom, } from '../utils/graphic';
|
|
4
4
|
import { MouseStatus } from '../enum/common';
|
|
5
|
-
|
|
6
|
-
export const usePanZoom = ({ canvasRef, imageRef, panZoomable = false, resetOnImageChange = true, }) => {
|
|
5
|
+
export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
|
|
7
6
|
const ZOOM_UNIT = 0.9;
|
|
8
7
|
const MAX_ZOOM = 4;
|
|
9
8
|
const MIN_ZOOM = 0.5;
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
const [canvasState, setCanvasState] = useState({
|
|
10
|
+
dx: 0,
|
|
11
|
+
dy: 0,
|
|
12
|
+
dw: 0,
|
|
13
|
+
dh: 0,
|
|
14
|
+
moveX: 0,
|
|
15
|
+
moveY: 0,
|
|
16
|
+
zoomX: 0,
|
|
17
|
+
zoomY: 0,
|
|
18
|
+
zoom: 0,
|
|
19
|
+
initZoom: 0,
|
|
20
|
+
});
|
|
21
21
|
const [status, setStatus] = useState('');
|
|
22
22
|
const [imageOnloadCount, setImageOnloadCount] = useState(0);
|
|
23
23
|
const [startMousePoint, setStartMousePoint] = useState();
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const point = canvasCenterPoint(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
24
|
+
const init = (canvas, image) => {
|
|
25
|
+
const point = canvasCenterPoint(canvas, image);
|
|
26
|
+
const zoomPoint = calculatorZoomPoint(canvas, 0, 0, point.x, point.y);
|
|
27
|
+
const initZoomValue = calculateInitZoom(canvas, image);
|
|
28
|
+
setCanvasState({
|
|
29
|
+
dx: point.x,
|
|
30
|
+
dy: point.y,
|
|
31
|
+
dw: image.width,
|
|
32
|
+
dh: image.height,
|
|
33
|
+
moveX: 0,
|
|
34
|
+
moveY: 0,
|
|
35
|
+
zoomX: zoomPoint.x,
|
|
36
|
+
zoomY: zoomPoint.y,
|
|
37
|
+
zoom: initZoomValue,
|
|
38
|
+
initZoom: initZoomValue,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const preserveZoomAndPosition = (canvas, image, zoomRatio) => {
|
|
42
|
+
const prevZoomRatio = zoomRatio || 1;
|
|
43
|
+
const point = canvasCenterPoint(canvas, image);
|
|
44
|
+
const newInitZoom = calculateInitZoom(canvas, image);
|
|
45
|
+
const zoomPoint = calculatorZoomPoint(canvas, canvasState.moveX, canvasState.moveY, point.x, point.y);
|
|
46
|
+
setCanvasState({
|
|
47
|
+
dx: point.x,
|
|
48
|
+
dy: point.y,
|
|
49
|
+
dw: image.width,
|
|
50
|
+
dh: image.height,
|
|
51
|
+
moveX: canvasState.moveX,
|
|
52
|
+
moveY: canvasState.moveY,
|
|
53
|
+
zoomX: zoomPoint.x,
|
|
54
|
+
zoomY: zoomPoint.y,
|
|
55
|
+
zoom: newInitZoom * prevZoomRatio,
|
|
56
|
+
initZoom: newInitZoom,
|
|
57
|
+
});
|
|
56
58
|
};
|
|
57
59
|
const initCanvas = () => {
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (!canvasRef.current)
|
|
61
|
+
return;
|
|
62
|
+
const canvas = resolutionCanvas(canvasRef.current);
|
|
63
|
+
if (!canvas)
|
|
64
|
+
return;
|
|
65
|
+
init(canvas, imageRef.current);
|
|
66
|
+
setImageOnloadCount((prev) => prev + 1);
|
|
67
|
+
};
|
|
68
|
+
const clearCanvas = () => {
|
|
69
|
+
if (!canvasRef.current)
|
|
70
|
+
return;
|
|
71
|
+
const canvas = resolutionCanvas(canvasRef.current);
|
|
72
|
+
if (!canvas)
|
|
73
|
+
return;
|
|
74
|
+
const ctx = canvas.getContext('2d');
|
|
75
|
+
ctx?.clearRect(0, 0, canvas.width, canvas.height);
|
|
64
76
|
};
|
|
65
77
|
const handleWheel = (event) => {
|
|
66
|
-
if (!panZoomable || !canvasRef.current ||
|
|
78
|
+
if (!panZoomable || !canvasRef.current || canvasState.initZoom <= 0)
|
|
67
79
|
return;
|
|
68
|
-
let
|
|
69
|
-
if (initZoom * MAX_ZOOM < zoom * ZOOM_UNIT)
|
|
70
|
-
|
|
71
|
-
if (initZoom * MIN_ZOOM > zoom * ZOOM_UNIT)
|
|
72
|
-
|
|
73
|
-
const zoomPoint = calculatorZoomPoint(canvasRef.current, moveX, moveY, dx, dy);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
let calcZoom = event.deltaY < 0 ? canvasState.zoom * (1 / ZOOM_UNIT) : canvasState.zoom * ZOOM_UNIT;
|
|
81
|
+
if (canvasState.initZoom * MAX_ZOOM < canvasState.zoom * ZOOM_UNIT)
|
|
82
|
+
calcZoom = calcZoom * ZOOM_UNIT;
|
|
83
|
+
if (canvasState.initZoom * MIN_ZOOM > canvasState.zoom * ZOOM_UNIT)
|
|
84
|
+
calcZoom = calcZoom * (1 / ZOOM_UNIT);
|
|
85
|
+
const zoomPoint = calculatorZoomPoint(canvasRef.current, canvasState.moveX, canvasState.moveY, canvasState.dx, canvasState.dy);
|
|
86
|
+
setCanvasState({
|
|
87
|
+
...canvasState,
|
|
88
|
+
zoomX: zoomPoint.x,
|
|
89
|
+
zoomY: zoomPoint.y,
|
|
90
|
+
zoom: calcZoom,
|
|
91
|
+
});
|
|
77
92
|
};
|
|
78
93
|
const handleMouseMove = (event) => {
|
|
79
|
-
if (!panZoomable || status !== MouseStatus.MOVE
|
|
94
|
+
if (!panZoomable || !canvasRef.current || status !== MouseStatus.MOVE)
|
|
80
95
|
return;
|
|
81
|
-
const
|
|
82
|
-
const rect =
|
|
96
|
+
const canvas = canvasRef.current;
|
|
97
|
+
const rect = canvas.getBoundingClientRect();
|
|
83
98
|
const mouseX = event.clientX - rect.left;
|
|
84
99
|
const mouseY = event.clientY - rect.top;
|
|
85
|
-
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1,
|
|
100
|
+
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: canvasState.moveX, y: canvasState.moveY }, { x: canvasState.zoomX, y: canvasState.zoomY }, { x: canvasState.dx, y: canvasState.dy }, canvasState.zoom || 1, canvas);
|
|
86
101
|
let x = mouse.x;
|
|
87
102
|
let y = mouse.y;
|
|
88
103
|
if (startMousePoint) {
|
|
89
104
|
x -= startMousePoint.x;
|
|
90
105
|
y -= startMousePoint.y;
|
|
91
106
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
const zoomPoint = calculatorZoomPoint(canvas, canvasState.moveX + x, canvasState.moveY + y, canvasState.dx, canvasState.dy);
|
|
108
|
+
setCanvasState({
|
|
109
|
+
...canvasState,
|
|
110
|
+
moveX: canvasState.moveX + x,
|
|
111
|
+
moveY: canvasState.moveY + y,
|
|
112
|
+
zoomX: zoomPoint.x,
|
|
113
|
+
zoomY: zoomPoint.y,
|
|
114
|
+
});
|
|
97
115
|
event.preventDefault();
|
|
98
116
|
};
|
|
99
117
|
const handleMouseDown = (event) => {
|
|
100
|
-
if (!panZoomable || !canvasRef.current
|
|
118
|
+
if (!panZoomable || !canvasRef.current)
|
|
101
119
|
return;
|
|
102
120
|
setStatus(MouseStatus.MOVE);
|
|
103
|
-
const
|
|
104
|
-
const rect =
|
|
121
|
+
const canvas = canvasRef.current;
|
|
122
|
+
const rect = canvas.getBoundingClientRect();
|
|
105
123
|
const mouseX = event.clientX - rect.left;
|
|
106
124
|
const mouseY = event.clientY - rect.top;
|
|
107
|
-
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1,
|
|
125
|
+
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: canvasState.moveX, y: canvasState.moveY }, { x: canvasState.zoomX, y: canvasState.zoomY }, { x: canvasState.dx, y: canvasState.dy }, canvasState.zoom || 1, canvas);
|
|
108
126
|
setStartMousePoint({ x: mouse.x, y: mouse.y });
|
|
109
127
|
event.preventDefault();
|
|
110
128
|
};
|
|
111
129
|
const handleMouseUp = (event) => {
|
|
112
|
-
if (!panZoomable)
|
|
130
|
+
if (!panZoomable || !canvasRef.current)
|
|
113
131
|
return;
|
|
114
132
|
setStatus(MouseStatus.STOP);
|
|
115
133
|
event.preventDefault();
|
|
116
134
|
};
|
|
117
135
|
const handleMouseLeave = (event) => {
|
|
118
|
-
if (!panZoomable)
|
|
136
|
+
if (!panZoomable || !canvasRef.current)
|
|
119
137
|
return;
|
|
120
138
|
setStatus(MouseStatus.STOP);
|
|
121
139
|
event.preventDefault();
|
|
122
140
|
};
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
const redraw = () => {
|
|
125
|
-
if (canvasRef.current && imageRef?.current) {
|
|
126
|
-
drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolutionCanvas(canvasRef.current), imageRef.current, true);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
redraw();
|
|
130
|
-
}, [moveX, moveY, zoomX, zoomY, zoom, dx, dy, dw, dh, imageOnloadCount, canvasRef, imageRef]);
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
if (!imageRef?.current || !canvasRef?.current || !width || !height)
|
|
133
|
-
return;
|
|
134
|
-
const canvasEl = resolutionCanvas(canvasRef.current);
|
|
135
|
-
if (!canvasEl)
|
|
136
|
-
return;
|
|
137
|
-
const isFirstLoad = imageOnloadCount === 0;
|
|
138
|
-
if (isFirstLoad) {
|
|
139
|
-
init(canvasEl, imageRef.current);
|
|
140
|
-
setImageOnloadCount((prev) => prev + 1);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
if (resetOnImageChange) {
|
|
144
|
-
init(canvasEl, imageRef.current);
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, canvasEl, imageRef.current, true);
|
|
148
|
-
}
|
|
149
|
-
setImageOnloadCount((prev) => prev + 1);
|
|
150
|
-
}, [imageRef?.current, width, height, resetOnImageChange]);
|
|
151
141
|
return {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
dy,
|
|
160
|
-
dw,
|
|
161
|
-
dh,
|
|
142
|
+
canvasState,
|
|
143
|
+
imageOnloadCount,
|
|
144
|
+
setImageOnloadCount,
|
|
145
|
+
init,
|
|
146
|
+
initCanvas,
|
|
147
|
+
clearCanvas,
|
|
148
|
+
preserveZoomAndPosition,
|
|
162
149
|
handleWheel,
|
|
163
150
|
handleMouseDown,
|
|
164
151
|
handleMouseMove,
|
|
165
152
|
handleMouseUp,
|
|
166
153
|
handleMouseLeave,
|
|
167
|
-
initCanvas,
|
|
168
154
|
};
|
|
169
|
-
}
|
|
155
|
+
}
|
|
@@ -14,14 +14,14 @@ const useResizeObserver = (opts = {}) => {
|
|
|
14
14
|
didUnmount.current = true;
|
|
15
15
|
};
|
|
16
16
|
}, []);
|
|
17
|
-
const refCallback = useResolvedElement(useCallback(element => {
|
|
17
|
+
const refCallback = useResolvedElement(useCallback((element) => {
|
|
18
18
|
if (!resizeObserverRef.current ||
|
|
19
19
|
resizeObserverRef.current.box !== opts.box ||
|
|
20
20
|
resizeObserverRef.current.round !== round) {
|
|
21
21
|
resizeObserverRef.current = {
|
|
22
22
|
box: opts.box,
|
|
23
23
|
round,
|
|
24
|
-
instance: new ResizeObserver(entries => {
|
|
24
|
+
instance: new ResizeObserver((entries) => {
|
|
25
25
|
const entry = entries[0];
|
|
26
26
|
const boxProp = opts.box === 'border-box'
|
|
27
27
|
? 'borderBoxSize'
|
|
@@ -102,7 +102,7 @@ function useResolvedElement(subscriber, refOrElement) {
|
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
}, []);
|
|
105
|
-
return useCallback(element => {
|
|
105
|
+
return useCallback((element) => {
|
|
106
106
|
cbElementRef.current = element;
|
|
107
107
|
evaluateSubscription();
|
|
108
108
|
}, [evaluateSubscription]);
|
package/dist/utils/graphic.d.ts
CHANGED
|
@@ -3,6 +3,11 @@ export declare const canvasCenterPoint: (canvas: HTMLCanvasElement, image: HTMLI
|
|
|
3
3
|
x: number;
|
|
4
4
|
y: number;
|
|
5
5
|
};
|
|
6
|
+
export declare const calculatorZoomPoint: (canvas: HTMLCanvasElement, movementX: number, movementY: number, dx: number, dy: number) => {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const calculateInitZoom: (canvas: HTMLCanvasElement, image: HTMLImageElement) => number;
|
|
6
11
|
export declare const drawCanvas: (moveX: number, moveY: number, zoomX: number, zoomY: number, zoom: number, dx: number, dy: number, canvas_el?: HTMLCanvasElement, image?: HTMLImageElement, isClear?: boolean) => void;
|
|
7
12
|
export declare const drawRect: (context: CanvasRenderingContext2D, coordinate: Coordinate) => void;
|
|
8
13
|
export declare const setRectangleStyle: (context: CanvasRenderingContext2D, coordinate: Coordinate) => void;
|
package/dist/utils/graphic.js
CHANGED
|
@@ -5,6 +5,22 @@ export const canvasCenterPoint = (canvas, image) => {
|
|
|
5
5
|
const centerY = canvas.height / 2 - image.height / 2;
|
|
6
6
|
return { x: centerX, y: centerY };
|
|
7
7
|
};
|
|
8
|
+
export const calculatorZoomPoint = (canvas, movementX, movementY, dx, dy) => {
|
|
9
|
+
let x = 0;
|
|
10
|
+
let y = 0;
|
|
11
|
+
if (canvas) {
|
|
12
|
+
x = -dx - movementX + canvas.width / 2;
|
|
13
|
+
y = -dy - movementY + canvas.height / 2;
|
|
14
|
+
}
|
|
15
|
+
return { x, y };
|
|
16
|
+
};
|
|
17
|
+
export const calculateInitZoom = (canvas, image) => {
|
|
18
|
+
if (!canvas || !image.naturalWidth)
|
|
19
|
+
return 1;
|
|
20
|
+
const canvasRatio = canvas.clientWidth / canvas.clientHeight;
|
|
21
|
+
const imageRatio = image.naturalWidth / image.naturalHeight;
|
|
22
|
+
return canvasRatio < imageRatio ? canvas.clientWidth / image.naturalWidth : canvas.clientHeight / image.naturalHeight;
|
|
23
|
+
};
|
|
8
24
|
// [캔버스] 그리기
|
|
9
25
|
export const drawCanvas = (moveX, moveY, zoomX, zoomY, zoom, dx, dy, canvas_el, image, isClear) => {
|
|
10
26
|
if (canvas_el) {
|
package/package.json
CHANGED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { RefObject } from 'react';
|
|
2
|
-
export declare const useImageLoader: (src: string, imageRef: RefObject<HTMLImageElement>, timeout?: number, onLoadSuccess?: () => void, onLoadError?: (error: Error) => void) => {
|
|
3
|
-
image: HTMLImageElement | null;
|
|
4
|
-
isLoading: boolean;
|
|
5
|
-
error: string | null;
|
|
6
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
export const useImageLoader = (src, imageRef, timeout = 10000, onLoadSuccess, onLoadError) => {
|
|
4
|
-
const [image, setImage] = useState(null);
|
|
5
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
6
|
-
const [error, setError] = useState(null);
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
if (!src || src.trim() === '') {
|
|
9
|
-
setImage(null);
|
|
10
|
-
setIsLoading(false);
|
|
11
|
-
setError(null);
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (imageRef.current && imageRef.current.src === src) {
|
|
15
|
-
setImage(imageRef.current);
|
|
16
|
-
setIsLoading(false);
|
|
17
|
-
setError(null);
|
|
18
|
-
onLoadSuccess?.();
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
setIsLoading(true);
|
|
22
|
-
setError(null);
|
|
23
|
-
setImage(null);
|
|
24
|
-
const controller = new AbortController();
|
|
25
|
-
const { signal } = controller;
|
|
26
|
-
const img = new Image();
|
|
27
|
-
img.crossOrigin = 'anonymous';
|
|
28
|
-
img.src = src;
|
|
29
|
-
const timeoutId = setTimeout(() => {
|
|
30
|
-
if (!signal.aborted) {
|
|
31
|
-
controller.abort();
|
|
32
|
-
const e = new Error(`Image load timeout: ${src}`);
|
|
33
|
-
setError(e.message);
|
|
34
|
-
setIsLoading(false);
|
|
35
|
-
onLoadError?.(e);
|
|
36
|
-
}
|
|
37
|
-
}, timeout);
|
|
38
|
-
const handleLoad = () => {
|
|
39
|
-
if (signal.aborted)
|
|
40
|
-
return;
|
|
41
|
-
clearTimeout(timeoutId);
|
|
42
|
-
imageRef.current = img;
|
|
43
|
-
setImage(img);
|
|
44
|
-
setIsLoading(false);
|
|
45
|
-
setError(null);
|
|
46
|
-
onLoadSuccess?.();
|
|
47
|
-
};
|
|
48
|
-
const handleError = (e) => {
|
|
49
|
-
if (signal.aborted)
|
|
50
|
-
return;
|
|
51
|
-
clearTimeout(timeoutId);
|
|
52
|
-
const err = e instanceof Error ? e : new Error(`Failed to load image: ${src}`);
|
|
53
|
-
setError(err.message);
|
|
54
|
-
setIsLoading(false);
|
|
55
|
-
onLoadError?.(err);
|
|
56
|
-
};
|
|
57
|
-
img.onload = handleLoad;
|
|
58
|
-
img.onerror = handleError;
|
|
59
|
-
return () => {
|
|
60
|
-
clearTimeout(timeoutId);
|
|
61
|
-
controller.abort();
|
|
62
|
-
img.onload = null;
|
|
63
|
-
img.onerror = null;
|
|
64
|
-
try {
|
|
65
|
-
img.src = '';
|
|
66
|
-
}
|
|
67
|
-
catch { }
|
|
68
|
-
};
|
|
69
|
-
}, [src, imageRef, timeout, onLoadSuccess, onLoadError]);
|
|
70
|
-
return { image, isLoading, error };
|
|
71
|
-
};
|