@deepnoid/canvas 0.1.20 → 0.1.22
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,181 @@
|
|
|
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
|
-
if (!canvasEl)
|
|
52
|
-
return;
|
|
53
|
-
const canvasWidth = canvasEl.clientWidth;
|
|
54
|
-
const canvasHeight = canvasEl.clientHeight;
|
|
55
|
-
let scale = 1;
|
|
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;
|
|
58
39
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
else {
|
|
41
|
+
return canvasEl.clientWidth / canvasEl.clientHeight > image.width / image.height
|
|
42
|
+
? canvasEl.clientHeight / image.height
|
|
43
|
+
: canvasEl.clientWidth / image.width;
|
|
44
|
+
}
|
|
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
|
+
if (!resetOnImageChange) {
|
|
95
|
+
const canvasEl = resolutionCanvas(canvasRef.current);
|
|
96
|
+
if (canvasEl)
|
|
97
|
+
init(canvasEl, imageRef.current);
|
|
98
|
+
setImageOnloadCount((prev) => prev + 1);
|
|
99
|
+
}
|
|
105
100
|
}
|
|
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)
|
|
101
|
+
}, [width, height]);
|
|
102
|
+
const handleWheel = (event) => {
|
|
103
|
+
if (!panZoomable)
|
|
119
104
|
return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
const
|
|
134
|
-
if (!panZoomable
|
|
105
|
+
if (canvasRef.current) {
|
|
106
|
+
let calc_zoom = event.deltaY < 0 ? (zoom || 1) * (1 / ZOOM_UNIT) : (zoom || 1) * ZOOM_UNIT;
|
|
107
|
+
if (initZoom * MAX_ZOOM < zoom * ZOOM_UNIT)
|
|
108
|
+
calc_zoom = calc_zoom * ZOOM_UNIT;
|
|
109
|
+
if (initZoom * MIN_ZOOM > zoom * ZOOM_UNIT)
|
|
110
|
+
calc_zoom = calc_zoom * (1 / ZOOM_UNIT);
|
|
111
|
+
const canvasEl = canvasRef.current;
|
|
112
|
+
const zoomPoint = calculatorZoomPoint(canvasEl, moveX, moveY, dx, dy);
|
|
113
|
+
setZoomX(zoomPoint.x);
|
|
114
|
+
setZoomY(zoomPoint.y);
|
|
115
|
+
setZoom(calc_zoom);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const handleMouseMove = (event) => {
|
|
119
|
+
if (!panZoomable)
|
|
135
120
|
return;
|
|
121
|
+
if (status === MouseStatus.MOVE) {
|
|
122
|
+
if (canvasRef.current) {
|
|
123
|
+
const canvasEl = canvasRef.current;
|
|
124
|
+
const rect = canvasEl.getBoundingClientRect();
|
|
125
|
+
const mouseX = event.clientX - rect.left;
|
|
126
|
+
const mouseY = event.clientY - rect.top;
|
|
127
|
+
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
|
|
128
|
+
let x = mouse.x;
|
|
129
|
+
let y = mouse.y;
|
|
130
|
+
if (startMousePoint) {
|
|
131
|
+
x = x - startMousePoint.x;
|
|
132
|
+
y = y - startMousePoint.y;
|
|
133
|
+
}
|
|
134
|
+
setMoveX(moveX + x);
|
|
135
|
+
setMoveY(moveY + y);
|
|
136
|
+
const zoomPoint = calculatorZoomPoint(canvasEl, moveX + x, moveY + y, dx, dy);
|
|
137
|
+
setZoomX(zoomPoint.x);
|
|
138
|
+
setZoomY(zoomPoint.y);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
};
|
|
143
|
+
const handleMouseDown = (event) => {
|
|
144
|
+
if (!panZoomable)
|
|
145
|
+
return false;
|
|
136
146
|
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);
|
|
147
|
+
if (canvasRef.current) {
|
|
148
|
+
const canvasEl = canvasRef.current;
|
|
149
|
+
const rect = canvasEl.getBoundingClientRect();
|
|
150
|
+
const mouseX = event.clientX - rect.left;
|
|
151
|
+
const mouseY = event.clientY - rect.top;
|
|
152
|
+
const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
|
|
153
|
+
const x = mouse.x;
|
|
154
|
+
const y = mouse.y;
|
|
155
|
+
setStartMousePoint({
|
|
156
|
+
x: x,
|
|
157
|
+
y: y,
|
|
158
|
+
});
|
|
163
159
|
}
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
const handleMouseUp =
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
};
|
|
162
|
+
const handleMouseUp = (event) => {
|
|
167
163
|
if (!panZoomable)
|
|
168
164
|
return;
|
|
169
165
|
setStatus(MouseStatus.STOP);
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
};
|
|
168
|
+
const handleMouseLeave = (event) => {
|
|
172
169
|
if (!panZoomable)
|
|
173
170
|
return;
|
|
174
171
|
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
|
-
}, []);
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
};
|
|
205
174
|
return {
|
|
206
175
|
moveX,
|
|
207
176
|
moveY,
|
|
208
177
|
zoom,
|
|
178
|
+
initZoom,
|
|
209
179
|
zoomX,
|
|
210
180
|
zoomY,
|
|
211
181
|
dx,
|
|
@@ -217,6 +187,6 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
|
|
|
217
187
|
handleMouseMove,
|
|
218
188
|
handleMouseUp,
|
|
219
189
|
handleMouseLeave,
|
|
220
|
-
|
|
190
|
+
initCanvas,
|
|
221
191
|
};
|
|
222
192
|
};
|