@deepnoid/canvas 0.1.20 → 0.1.21
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.
|
@@ -8,29 +8,29 @@ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton,
|
|
|
8
8
|
const canvasRef = useRef(null);
|
|
9
9
|
const [displayCoordinates, setDisplayCoordinates] = useState();
|
|
10
10
|
const { image: loadedImage, isLoading, error } = useImageLoader(image, 10000, onImageLoadSuccess, onImageLoadError);
|
|
11
|
-
const { moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave,
|
|
11
|
+
const { moveX, moveY, zoom, initZoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, initCanvas, } = usePanZoom({
|
|
12
12
|
canvasRef,
|
|
13
13
|
image: loadedImage,
|
|
14
14
|
panZoomable,
|
|
15
15
|
resetOnImageChange,
|
|
16
16
|
});
|
|
17
|
+
const loaded = loadedImage && !isLoading;
|
|
17
18
|
useEffect(() => {
|
|
18
|
-
if (
|
|
19
|
+
if (loaded) {
|
|
19
20
|
setDisplayCoordinates(coordinates);
|
|
20
21
|
}
|
|
21
|
-
}, [
|
|
22
|
+
}, [loaded, coordinates]);
|
|
22
23
|
useEffect(() => {
|
|
23
24
|
if (isLoading) {
|
|
24
25
|
setDisplayCoordinates(undefined);
|
|
25
26
|
}
|
|
26
27
|
}, [isLoading]);
|
|
27
|
-
if (!image ||
|
|
28
|
+
if (!image || error)
|
|
28
29
|
return null;
|
|
29
|
-
}
|
|
30
30
|
return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children: _jsx("div", { onWheel: handleWheel, onMouseMove: handleMouseMove, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, style: {
|
|
31
31
|
flex: 1,
|
|
32
32
|
position: 'relative',
|
|
33
33
|
cursor: panZoomable ? 'grab' : 'default',
|
|
34
|
-
}, children:
|
|
34
|
+
}, children: loaded && (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 } }), _jsx(Canvas, { moveX: moveX, moveY: moveY, zoomX: zoomX, zoomY: zoomY, zoom: zoom, dx: dx, dy: dy, dw: dw, dh: dh, coordinates: displayCoordinates, editable: editable }), ZoomButton && initZoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((zoom / initZoom) * 100)}%` }))] })) }) }));
|
|
35
35
|
};
|
|
36
36
|
export default AnnotatedCanvas;
|
|
@@ -9,17 +9,18 @@ export declare const usePanZoom: ({ canvasRef, image, panZoomable, resetOnImageC
|
|
|
9
9
|
moveX: number;
|
|
10
10
|
moveY: number;
|
|
11
11
|
zoom: number;
|
|
12
|
+
initZoom: number;
|
|
12
13
|
zoomX: number;
|
|
13
14
|
zoomY: number;
|
|
14
15
|
dx: number;
|
|
15
16
|
dy: number;
|
|
16
17
|
dw: number;
|
|
17
18
|
dh: number;
|
|
18
|
-
handleWheel: (
|
|
19
|
-
handleMouseDown: (
|
|
20
|
-
handleMouseMove: (
|
|
21
|
-
handleMouseUp: () => void;
|
|
22
|
-
handleMouseLeave: () => void;
|
|
23
|
-
|
|
19
|
+
handleWheel: (event: WheelEvent) => void;
|
|
20
|
+
handleMouseDown: (event: MouseEvent) => false | undefined;
|
|
21
|
+
handleMouseMove: (event: MouseEvent) => void;
|
|
22
|
+
handleMouseUp: (event: MouseEvent) => void;
|
|
23
|
+
handleMouseLeave: (event: MouseEvent) => void;
|
|
24
|
+
initCanvas: () => void;
|
|
24
25
|
};
|
|
25
26
|
export {};
|
package/dist/hooks/usePanZoom.js
CHANGED
|
@@ -1,211 +1,179 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useCallback, useEffect, useState, useRef } from 'react';
|
|
3
|
-
import { resolutionCanvas, drawCanvas, getMousePointTransform } from '../utils/graphic';
|
|
3
|
+
import { resolutionCanvas, drawCanvas, getMousePointTransform, canvasCenterPoint } from '../utils/graphic';
|
|
4
4
|
import { MouseStatus } from '../enum/common';
|
|
5
|
+
import useResizeObserver from './useResizeObserver';
|
|
5
6
|
export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImageChange = true }) => {
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const ZOOM_UNIT = 0.9;
|
|
8
|
+
const MAX_ZOOM = 4;
|
|
9
|
+
const MIN_ZOOM = 0.5;
|
|
10
|
+
const { width, height } = useResizeObserver({ ref: canvasRef });
|
|
11
|
+
const [initZoom, setInitZoom] = useState(0);
|
|
12
|
+
const [zoom, setZoom] = useState(0);
|
|
9
13
|
const [zoomX, setZoomX] = useState(0);
|
|
10
14
|
const [zoomY, setZoomY] = useState(0);
|
|
11
15
|
const [dx, setDx] = useState(0);
|
|
12
16
|
const [dy, setDy] = useState(0);
|
|
13
17
|
const [dw, setDw] = useState(0);
|
|
14
18
|
const [dh, setDh] = useState(0);
|
|
15
|
-
const [
|
|
16
|
-
const [
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const ZOOM_UNIT = 0.9;
|
|
28
|
-
const MAX_ZOOM = 4;
|
|
29
|
-
const MIN_ZOOM = 0.5;
|
|
30
|
-
const prevImageRef = useRef(null);
|
|
31
|
-
const immediateRender = useCallback((moveX, moveY, zoomX, zoomY, zoom, dx, dy) => {
|
|
32
|
-
const canvasEl = canvasRef.current;
|
|
33
|
-
if (!canvasEl || !image)
|
|
34
|
-
return;
|
|
35
|
-
const resolvedCanvas = resolutionCanvas(canvasEl);
|
|
36
|
-
if (!resolvedCanvas)
|
|
37
|
-
return;
|
|
38
|
-
drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolvedCanvas, image, true);
|
|
39
|
-
}, [canvasRef, image]);
|
|
40
|
-
const scheduleRender = useCallback(() => {
|
|
41
|
-
if (animationFrameRef.current) {
|
|
42
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
19
|
+
const [moveX, setMoveX] = useState(0);
|
|
20
|
+
const [moveY, setMoveY] = useState(0);
|
|
21
|
+
const [status, setStatus] = useState('');
|
|
22
|
+
const [imageOnloadCount, setImageOnloadCount] = useState(0);
|
|
23
|
+
const [_imageInfo, _setImageInfo] = useState();
|
|
24
|
+
const imageRef = useRef(new Image());
|
|
25
|
+
const calculatorZoomPoint = useCallback((canvasEl, movementX, movementY, dx, dy) => {
|
|
26
|
+
let x = 0;
|
|
27
|
+
let y = 0;
|
|
28
|
+
if (canvasRef.current) {
|
|
29
|
+
x = -dx - movementX + canvasEl.width / 2;
|
|
30
|
+
y = -dy - movementY + canvasEl.height / 2;
|
|
43
31
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (img.width > canvasWidth || img.height > canvasHeight) {
|
|
57
|
-
scale = Math.min(canvasWidth / img.width, canvasHeight / img.height);
|
|
32
|
+
return { x, y };
|
|
33
|
+
}, []);
|
|
34
|
+
const calculateInitZoom = useCallback((image, canvasEl) => {
|
|
35
|
+
if (canvasEl.clientWidth < canvasEl.clientHeight) {
|
|
36
|
+
return canvasEl.clientWidth / canvasEl.clientHeight < image.width / image.height
|
|
37
|
+
? canvasEl.clientWidth / image.width
|
|
38
|
+
: canvasEl.clientHeight / image.height;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return canvasEl.clientWidth / canvasEl.clientHeight > image.width / image.height
|
|
42
|
+
? canvasEl.clientHeight / image.height
|
|
43
|
+
: canvasEl.clientWidth / image.width;
|
|
58
44
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
setDh(dh);
|
|
67
|
-
setZoom(scale);
|
|
68
|
-
setZoomX(0);
|
|
69
|
-
setZoomY(0);
|
|
45
|
+
}, []);
|
|
46
|
+
const init = (canvasEl, image) => {
|
|
47
|
+
const point = canvasCenterPoint(canvasEl, image);
|
|
48
|
+
setDx(point.x);
|
|
49
|
+
setDy(point.y);
|
|
50
|
+
setDw(image.width);
|
|
51
|
+
setDh(image.height);
|
|
70
52
|
setMoveX(0);
|
|
71
53
|
setMoveY(0);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
54
|
+
const init_zoom = calculateInitZoom(image, canvasEl);
|
|
55
|
+
setZoom(init_zoom);
|
|
56
|
+
setInitZoom(init_zoom);
|
|
57
|
+
const zoomPoint = calculatorZoomPoint(canvasEl, 0, 0, point.x, point.y);
|
|
58
|
+
setZoomX(zoomPoint.x);
|
|
59
|
+
setZoomY(zoomPoint.y);
|
|
60
|
+
};
|
|
61
|
+
const initCanvas = () => {
|
|
62
|
+
if (image) {
|
|
63
|
+
const canvasEl = resolutionCanvas(canvasRef.current);
|
|
64
|
+
if (canvasEl)
|
|
65
|
+
init(canvasEl, imageRef.current);
|
|
66
|
+
setImageOnloadCount((prev) => prev + 1);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
82
69
|
useEffect(() => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
70
|
+
if (image) {
|
|
71
|
+
imageRef.current.src = image.src;
|
|
72
|
+
imageRef.current.onload = () => {
|
|
73
|
+
if (resetOnImageChange) {
|
|
74
|
+
const canvasEl = resolutionCanvas(canvasRef.current);
|
|
75
|
+
if (canvasEl)
|
|
76
|
+
init(canvasEl, imageRef.current);
|
|
77
|
+
}
|
|
78
|
+
setImageOnloadCount((prev) => prev + 1);
|
|
79
|
+
};
|
|
92
80
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
81
|
+
}, [image]);
|
|
82
|
+
const [startMousePoint, setStartMousePoint] = useState();
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const redraw = () => {
|
|
85
|
+
if (canvasRef.current) {
|
|
86
|
+
drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolutionCanvas(canvasRef.current), imageRef.current, true);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
redraw();
|
|
90
|
+
}, [moveX, moveY, zoomX, zoomY, zoom, dx, dy, dw, dh, imageOnloadCount]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (image) {
|
|
93
|
+
imageRef.current.src = image.src;
|
|
94
|
+
const canvasEl = resolutionCanvas(canvasRef.current);
|
|
95
|
+
if (canvasEl)
|
|
96
|
+
init(canvasEl, imageRef.current);
|
|
97
|
+
setImageOnloadCount((prev) => prev + 1);
|
|
105
98
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const canvasEl = canvasRef.current;
|
|
110
|
-
if (!panZoomable || !canvasRef.current || !canvasEl || !image)
|
|
111
|
-
return;
|
|
112
|
-
const currentZoom = currentTransformRef.current.zoom;
|
|
113
|
-
let nextZoom = e.deltaY < 0 ? currentZoom / ZOOM_UNIT : currentZoom * ZOOM_UNIT;
|
|
114
|
-
nextZoom = Math.min(Math.max(nextZoom, MIN_ZOOM), MAX_ZOOM);
|
|
115
|
-
if ((currentZoom <= MIN_ZOOM && e.deltaY > 0) || (currentZoom >= MAX_ZOOM && e.deltaY < 0))
|
|
116
|
-
return;
|
|
117
|
-
const resolvedCanvas = resolutionCanvas(canvasEl);
|
|
118
|
-
if (!resolvedCanvas)
|
|
99
|
+
}, [width, height]);
|
|
100
|
+
const handleWheel = (event) => {
|
|
101
|
+
if (!panZoomable)
|
|
119
102
|
return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
const
|
|
134
|
-
if (!panZoomable
|
|
103
|
+
if (canvasRef.current) {
|
|
104
|
+
let calc_zoom = event.deltaY < 0 ? (zoom || 1) * (1 / ZOOM_UNIT) : (zoom || 1) * ZOOM_UNIT;
|
|
105
|
+
if (initZoom * MAX_ZOOM < zoom * ZOOM_UNIT)
|
|
106
|
+
calc_zoom = calc_zoom * ZOOM_UNIT;
|
|
107
|
+
if (initZoom * MIN_ZOOM > zoom * ZOOM_UNIT)
|
|
108
|
+
calc_zoom = calc_zoom * (1 / ZOOM_UNIT);
|
|
109
|
+
const canvasEl = canvasRef.current;
|
|
110
|
+
const zoomPoint = calculatorZoomPoint(canvasEl, moveX, moveY, dx, dy);
|
|
111
|
+
setZoomX(zoomPoint.x);
|
|
112
|
+
setZoomY(zoomPoint.y);
|
|
113
|
+
setZoom(calc_zoom);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const handleMouseMove = (event) => {
|
|
117
|
+
if (!panZoomable)
|
|
135
118
|
return;
|
|
119
|
+
if (status === MouseStatus.MOVE) {
|
|
120
|
+
if (canvasRef.current) {
|
|
121
|
+
const canvasEl = canvasRef.current;
|
|
122
|
+
const rect = canvasEl.getBoundingClientRect();
|
|
123
|
+
const mouseX = event.clientX - rect.left;
|
|
124
|
+
const mouseY = event.clientY - rect.top;
|
|
125
|
+
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
|
|
126
|
+
let x = mouse.x;
|
|
127
|
+
let y = mouse.y;
|
|
128
|
+
if (startMousePoint) {
|
|
129
|
+
x = x - startMousePoint.x;
|
|
130
|
+
y = y - startMousePoint.y;
|
|
131
|
+
}
|
|
132
|
+
setMoveX(moveX + x);
|
|
133
|
+
setMoveY(moveY + y);
|
|
134
|
+
const zoomPoint = calculatorZoomPoint(canvasEl, moveX + x, moveY + y, dx, dy);
|
|
135
|
+
setZoomX(zoomPoint.x);
|
|
136
|
+
setZoomY(zoomPoint.y);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
};
|
|
141
|
+
const handleMouseDown = (event) => {
|
|
142
|
+
if (!panZoomable)
|
|
143
|
+
return false;
|
|
136
144
|
setStatus(MouseStatus.MOVE);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const mouseX = e.clientX - rect.left;
|
|
150
|
-
const mouseY = e.clientY - rect.top;
|
|
151
|
-
const current = currentTransformRef.current;
|
|
152
|
-
const transformed = getMousePointTransform({ x: mouseX, y: mouseY }, { x: current.moveX, y: current.moveY }, { x: current.zoomX, y: current.zoomY }, { x: current.dx, y: current.dy }, current.zoom, canvasRef.current);
|
|
153
|
-
if (startMousePoint) {
|
|
154
|
-
const deltaX = transformed.x - startMousePoint.x;
|
|
155
|
-
const deltaY = transformed.y - startMousePoint.y;
|
|
156
|
-
const newMoveX = current.moveX + deltaX;
|
|
157
|
-
const newMoveY = current.moveY + deltaY;
|
|
158
|
-
currentTransformRef.current.moveX = newMoveX;
|
|
159
|
-
currentTransformRef.current.moveY = newMoveY;
|
|
160
|
-
immediateRender(newMoveX, newMoveY, current.zoomX, current.zoomY, current.zoom, current.dx, current.dy);
|
|
161
|
-
setMoveX(newMoveX);
|
|
162
|
-
setMoveY(newMoveY);
|
|
145
|
+
if (canvasRef.current) {
|
|
146
|
+
const canvasEl = canvasRef.current;
|
|
147
|
+
const rect = canvasEl.getBoundingClientRect();
|
|
148
|
+
const mouseX = event.clientX - rect.left;
|
|
149
|
+
const mouseY = event.clientY - rect.top;
|
|
150
|
+
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
|
|
151
|
+
const x = mouse.x;
|
|
152
|
+
const y = mouse.y;
|
|
153
|
+
setStartMousePoint({
|
|
154
|
+
x: x,
|
|
155
|
+
y: y,
|
|
156
|
+
});
|
|
163
157
|
}
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
const handleMouseUp =
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
};
|
|
160
|
+
const handleMouseUp = (event) => {
|
|
167
161
|
if (!panZoomable)
|
|
168
162
|
return;
|
|
169
163
|
setStatus(MouseStatus.STOP);
|
|
170
|
-
|
|
171
|
-
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
};
|
|
166
|
+
const handleMouseLeave = (event) => {
|
|
172
167
|
if (!panZoomable)
|
|
173
168
|
return;
|
|
174
169
|
setStatus(MouseStatus.STOP);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (!canvasRef.current || !image)
|
|
178
|
-
return;
|
|
179
|
-
if (dx !== 0 || dy !== 0 || zoom !== 1) {
|
|
180
|
-
scheduleRender();
|
|
181
|
-
}
|
|
182
|
-
}, [canvasRef, image, moveX, moveY, zoomX, zoomY, zoom, dx, dy, scheduleRender]);
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
currentTransformRef.current = {
|
|
185
|
-
moveX,
|
|
186
|
-
moveY,
|
|
187
|
-
zoom,
|
|
188
|
-
zoomX,
|
|
189
|
-
zoomY,
|
|
190
|
-
dx,
|
|
191
|
-
dy,
|
|
192
|
-
};
|
|
193
|
-
}, [moveX, moveY, zoom, zoomX, zoomY, dx, dy]);
|
|
194
|
-
const resetView = useCallback(() => {
|
|
195
|
-
if (image)
|
|
196
|
-
initCanvas(image);
|
|
197
|
-
}, [image, initCanvas]);
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
return () => {
|
|
200
|
-
if (animationFrameRef.current) {
|
|
201
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
}, []);
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
};
|
|
205
172
|
return {
|
|
206
173
|
moveX,
|
|
207
174
|
moveY,
|
|
208
175
|
zoom,
|
|
176
|
+
initZoom,
|
|
209
177
|
zoomX,
|
|
210
178
|
zoomY,
|
|
211
179
|
dx,
|
|
@@ -217,6 +185,6 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
|
|
|
217
185
|
handleMouseMove,
|
|
218
186
|
handleMouseUp,
|
|
219
187
|
handleMouseLeave,
|
|
220
|
-
|
|
188
|
+
initCanvas,
|
|
221
189
|
};
|
|
222
190
|
};
|