Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 6765bb8

Browse files
authored
Merge pull request #6418 from robintown/zoom-to-cursor
2 parents 09186f8 + 5d4b293 commit 6765bb8

File tree

1 file changed

+58
-23
lines changed

1 file changed

+58
-23
lines changed

src/components/views/elements/ImageView.tsx

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -160,41 +160,80 @@ export default class ImageView extends React.Component<IProps, IState> {
160160
});
161161
};
162162

163-
private zoom(delta: number) {
164-
const newZoom = this.state.zoom + delta;
163+
private zoomDelta(delta: number, anchorX?: number, anchorY?: number) {
164+
this.zoom(this.state.zoom + delta, anchorX, anchorY);
165+
}
166+
167+
private zoom(zoomLevel: number, anchorX?: number, anchorY?: number) {
168+
const oldZoom = this.state.zoom;
169+
const newZoom = Math.min(zoomLevel, this.state.maxZoom);
165170

166171
if (newZoom <= this.state.minZoom) {
172+
// Zoom out fully
167173
this.setState({
168174
zoom: this.state.minZoom,
169175
translationX: 0,
170176
translationY: 0,
171177
});
172-
return;
173-
}
174-
if (newZoom >= this.state.maxZoom) {
175-
this.setState({ zoom: this.state.maxZoom });
176-
return;
177-
}
178+
} else if (typeof anchorX !== "number" && typeof anchorY !== "number") {
179+
// Zoom relative to the center of the view
180+
this.setState({
181+
zoom: newZoom,
182+
translationX: this.state.translationX * newZoom / oldZoom,
183+
translationY: this.state.translationY * newZoom / oldZoom,
184+
});
185+
} else {
186+
// Zoom relative to the given point on the image.
187+
// First we need to figure out the offset of the anchor point
188+
// relative to the center of the image, accounting for rotation.
189+
let offsetX;
190+
let offsetY;
191+
// The modulo operator can return negative values for some
192+
// rotations, so we have to do some extra work to normalize it
193+
switch (((this.state.rotation % 360) + 360) % 360) {
194+
case 0:
195+
offsetX = this.image.current.clientWidth / 2 - anchorX;
196+
offsetY = this.image.current.clientHeight / 2 - anchorY;
197+
break;
198+
case 90:
199+
offsetX = anchorY - this.image.current.clientHeight / 2;
200+
offsetY = this.image.current.clientWidth / 2 - anchorX;
201+
break;
202+
case 180:
203+
offsetX = anchorX - this.image.current.clientWidth / 2;
204+
offsetY = anchorY - this.image.current.clientHeight / 2;
205+
break;
206+
case 270:
207+
offsetX = this.image.current.clientHeight / 2 - anchorY;
208+
offsetY = anchorX - this.image.current.clientWidth / 2;
209+
}
178210

179-
this.setState({
180-
zoom: newZoom,
181-
});
211+
// Apply the zoom and offset
212+
this.setState({
213+
zoom: newZoom,
214+
translationX: this.state.translationX + (newZoom - oldZoom) * offsetX,
215+
translationY: this.state.translationY + (newZoom - oldZoom) * offsetY,
216+
});
217+
}
182218
}
183219

184220
private onWheel = (ev: WheelEvent) => {
185-
ev.stopPropagation();
186-
ev.preventDefault();
221+
if (ev.target === this.image.current) {
222+
ev.stopPropagation();
223+
ev.preventDefault();
187224

188-
const { deltaY } = normalizeWheelEvent(ev);
189-
this.zoom(-(deltaY * ZOOM_COEFFICIENT));
225+
const { deltaY } = normalizeWheelEvent(ev);
226+
// Zoom in on the point on the image targeted by the cursor
227+
this.zoomDelta(-deltaY * ZOOM_COEFFICIENT, ev.offsetX, ev.offsetY);
228+
}
190229
};
191230

192231
private onZoomInClick = () => {
193-
this.zoom(ZOOM_STEP);
232+
this.zoomDelta(ZOOM_STEP);
194233
};
195234

196235
private onZoomOutClick = () => {
197-
this.zoom(-ZOOM_STEP);
236+
this.zoomDelta(-ZOOM_STEP);
198237
};
199238

200239
private onKeyDown = (ev: KeyboardEvent) => {
@@ -259,7 +298,7 @@ export default class ImageView extends React.Component<IProps, IState> {
259298

260299
// Zoom in if we are completely zoomed out
261300
if (this.state.zoom === this.state.minZoom) {
262-
this.setState({ zoom: this.state.maxZoom });
301+
this.zoom(this.state.maxZoom, ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
263302
return;
264303
}
265304

@@ -289,11 +328,7 @@ export default class ImageView extends React.Component<IProps, IState> {
289328
Math.abs(this.state.translationX - this.previousX) < ZOOM_DISTANCE &&
290329
Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE
291330
) {
292-
this.setState({
293-
zoom: this.state.minZoom,
294-
translationX: 0,
295-
translationY: 0,
296-
});
331+
this.zoom(this.state.minZoom);
297332
this.initX = 0;
298333
this.initY = 0;
299334
}

0 commit comments

Comments
 (0)