@@ -160,41 +160,80 @@ export default class ImageView extends React.Component<IProps, IState> {
160
160
} ) ;
161
161
} ;
162
162
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 ) ;
165
170
166
171
if ( newZoom <= this . state . minZoom ) {
172
+ // Zoom out fully
167
173
this . setState ( {
168
174
zoom : this . state . minZoom ,
169
175
translationX : 0 ,
170
176
translationY : 0 ,
171
177
} ) ;
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
+ }
178
210
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
+ }
182
218
}
183
219
184
220
private onWheel = ( ev : WheelEvent ) => {
185
- ev . stopPropagation ( ) ;
186
- ev . preventDefault ( ) ;
221
+ if ( ev . target === this . image . current ) {
222
+ ev . stopPropagation ( ) ;
223
+ ev . preventDefault ( ) ;
187
224
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
+ }
190
229
} ;
191
230
192
231
private onZoomInClick = ( ) => {
193
- this . zoom ( ZOOM_STEP ) ;
232
+ this . zoomDelta ( ZOOM_STEP ) ;
194
233
} ;
195
234
196
235
private onZoomOutClick = ( ) => {
197
- this . zoom ( - ZOOM_STEP ) ;
236
+ this . zoomDelta ( - ZOOM_STEP ) ;
198
237
} ;
199
238
200
239
private onKeyDown = ( ev : KeyboardEvent ) => {
@@ -259,7 +298,7 @@ export default class ImageView extends React.Component<IProps, IState> {
259
298
260
299
// Zoom in if we are completely zoomed out
261
300
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 ) ;
263
302
return ;
264
303
}
265
304
@@ -289,11 +328,7 @@ export default class ImageView extends React.Component<IProps, IState> {
289
328
Math . abs ( this . state . translationX - this . previousX ) < ZOOM_DISTANCE &&
290
329
Math . abs ( this . state . translationY - this . previousY ) < ZOOM_DISTANCE
291
330
) {
292
- this . setState ( {
293
- zoom : this . state . minZoom ,
294
- translationX : 0 ,
295
- translationY : 0 ,
296
- } ) ;
331
+ this . zoom ( this . state . minZoom ) ;
297
332
this . initX = 0 ;
298
333
this . initY = 0 ;
299
334
}
0 commit comments