Merge pull request #6418 from robintown/zoom-to-cursor
This commit is contained in:
commit
6765bb8f4c
1 changed files with 58 additions and 23 deletions
|
@ -160,41 +160,80 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private zoom(delta: number) {
|
private zoomDelta(delta: number, anchorX?: number, anchorY?: number) {
|
||||||
const newZoom = this.state.zoom + delta;
|
this.zoom(this.state.zoom + delta, anchorX, anchorY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private zoom(zoomLevel: number, anchorX?: number, anchorY?: number) {
|
||||||
|
const oldZoom = this.state.zoom;
|
||||||
|
const newZoom = Math.min(zoomLevel, this.state.maxZoom);
|
||||||
|
|
||||||
if (newZoom <= this.state.minZoom) {
|
if (newZoom <= this.state.minZoom) {
|
||||||
|
// Zoom out fully
|
||||||
this.setState({
|
this.setState({
|
||||||
zoom: this.state.minZoom,
|
zoom: this.state.minZoom,
|
||||||
translationX: 0,
|
translationX: 0,
|
||||||
translationY: 0,
|
translationY: 0,
|
||||||
});
|
});
|
||||||
return;
|
} else if (typeof anchorX !== "number" && typeof anchorY !== "number") {
|
||||||
}
|
// Zoom relative to the center of the view
|
||||||
if (newZoom >= this.state.maxZoom) {
|
this.setState({
|
||||||
this.setState({ zoom: this.state.maxZoom });
|
zoom: newZoom,
|
||||||
return;
|
translationX: this.state.translationX * newZoom / oldZoom,
|
||||||
}
|
translationY: this.state.translationY * newZoom / oldZoom,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Zoom relative to the given point on the image.
|
||||||
|
// First we need to figure out the offset of the anchor point
|
||||||
|
// relative to the center of the image, accounting for rotation.
|
||||||
|
let offsetX;
|
||||||
|
let offsetY;
|
||||||
|
// The modulo operator can return negative values for some
|
||||||
|
// rotations, so we have to do some extra work to normalize it
|
||||||
|
switch (((this.state.rotation % 360) + 360) % 360) {
|
||||||
|
case 0:
|
||||||
|
offsetX = this.image.current.clientWidth / 2 - anchorX;
|
||||||
|
offsetY = this.image.current.clientHeight / 2 - anchorY;
|
||||||
|
break;
|
||||||
|
case 90:
|
||||||
|
offsetX = anchorY - this.image.current.clientHeight / 2;
|
||||||
|
offsetY = this.image.current.clientWidth / 2 - anchorX;
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
offsetX = anchorX - this.image.current.clientWidth / 2;
|
||||||
|
offsetY = anchorY - this.image.current.clientHeight / 2;
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
offsetX = this.image.current.clientHeight / 2 - anchorY;
|
||||||
|
offsetY = anchorX - this.image.current.clientWidth / 2;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
// Apply the zoom and offset
|
||||||
zoom: newZoom,
|
this.setState({
|
||||||
});
|
zoom: newZoom,
|
||||||
|
translationX: this.state.translationX + (newZoom - oldZoom) * offsetX,
|
||||||
|
translationY: this.state.translationY + (newZoom - oldZoom) * offsetY,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWheel = (ev: WheelEvent) => {
|
private onWheel = (ev: WheelEvent) => {
|
||||||
ev.stopPropagation();
|
if (ev.target === this.image.current) {
|
||||||
ev.preventDefault();
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
const { deltaY } = normalizeWheelEvent(ev);
|
const { deltaY } = normalizeWheelEvent(ev);
|
||||||
this.zoom(-(deltaY * ZOOM_COEFFICIENT));
|
// Zoom in on the point on the image targeted by the cursor
|
||||||
|
this.zoomDelta(-deltaY * ZOOM_COEFFICIENT, ev.offsetX, ev.offsetY);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onZoomInClick = () => {
|
private onZoomInClick = () => {
|
||||||
this.zoom(ZOOM_STEP);
|
this.zoomDelta(ZOOM_STEP);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onZoomOutClick = () => {
|
private onZoomOutClick = () => {
|
||||||
this.zoom(-ZOOM_STEP);
|
this.zoomDelta(-ZOOM_STEP);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onKeyDown = (ev: KeyboardEvent) => {
|
private onKeyDown = (ev: KeyboardEvent) => {
|
||||||
|
@ -259,7 +298,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// Zoom in if we are completely zoomed out
|
// Zoom in if we are completely zoomed out
|
||||||
if (this.state.zoom === this.state.minZoom) {
|
if (this.state.zoom === this.state.minZoom) {
|
||||||
this.setState({ zoom: this.state.maxZoom });
|
this.zoom(this.state.maxZoom, ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,11 +328,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
Math.abs(this.state.translationX - this.previousX) < ZOOM_DISTANCE &&
|
Math.abs(this.state.translationX - this.previousX) < ZOOM_DISTANCE &&
|
||||||
Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE
|
Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE
|
||||||
) {
|
) {
|
||||||
this.setState({
|
this.zoom(this.state.minZoom);
|
||||||
zoom: this.state.minZoom,
|
|
||||||
translationX: 0,
|
|
||||||
translationY: 0,
|
|
||||||
});
|
|
||||||
this.initX = 0;
|
this.initX = 0;
|
||||||
this.initY = 0;
|
this.initY = 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue