From c38032b334f402a5aa81114c1b1fb50eb16fad2d Mon Sep 17 00:00:00 2001 From: Kevin Keegan Date: Tue, 25 Oct 2022 17:24:36 -0700 Subject: [PATCH] Improve Zooming and Panning in Lightbox Allow setting a minumum Zoom-Out scale. Allow resetting the view to the initial view when ZoomMin has been reached. This recenters the image. Allow for RelativePinchtoZoom where successive pinching results in additional zooming rather than starting over each time. Define ZoomMax as where 1 pixel in the image equals 1 pixel on the display. Allow defining a ZoomOut scaling factor for pinch to zoom. Zooming by default is slow because its scale is [0,1] where as zooming in is [1,inf]. A scalling factor of 3 seems to create the expected zoom out functionality. Limit panning so that the edge of the image is not allowed to go past the center of the viewer. --- src/jquery.nanogallery2.core.js | 117 +++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/src/jquery.nanogallery2.core.js b/src/jquery.nanogallery2.core.js index 3aaf4ac9..3db11d93 100644 --- a/src/jquery.nanogallery2.core.js +++ b/src/jquery.nanogallery2.core.js @@ -1466,6 +1466,10 @@ imageTransition : 'swipe2', viewerTransitionMediaKind : 'img', viewerZoom : true, + viewerRelativePinchZoom : false, // If false, pinch to zoom will always start at 1. True makes it work like expected. + viewerMinZoom : 0.2, // The minimum zoom scale allowed + viewerResetZoomAtMin : false, // When mim zoom is reached reset the view to non-zoomed mode? + viewerZoomOutScale : 3.0, // Zoom out scale when using relative zooming, smaller numbers make zooming out slower viewerImageDisplay : '', openOnStart : '', viewerHideToolsDelay : 4000, @@ -2612,7 +2616,8 @@ posX: 0, // position to center zoom in/out posY: 0, userFactor: 1, // user zoom factor (applied to the baseZoom factor) - isZooming: false + isZooming: false, + lastScale: 1, // The last scale factor requested by pinch to zoom, used for relative zoom }, padding: { H: 0, V: 0 }, // padding for the image window: { lastWidth: 0, lastHeight: 0 }, @@ -8480,22 +8485,31 @@ } else { // zoom out - G.VOM.zoom.userFactor -= 0.1; - ViewerZoomMin(); + G.VOM.zoom.userFactor-=0.1; + if(ViewerZoomMin() && G.O.viewerResetZoomAtMin){ + // Reset view + G.VOM.zoom.isZooming = false; + ResizeLightbox(true); + }; } ViewerMediaSetPosAndZoom(); } function ViewerZoomMax() { - if( G.VOM.zoom.userFactor > 3 ) { - G.VOM.zoom.userFactor = 3; + var item = G.VOM.content.current.NGY2Item(); + var zoomBaseFactor = ViewerZoomBaseFactor(item); + + if( G.VOM.zoom.userFactor > (1 / zoomBaseFactor) ) { + G.VOM.zoom.userFactor = (1 / zoomBaseFactor); } } function ViewerZoomMin() { - if( G.VOM.zoom.userFactor < 0.2 ) { - G.VOM.zoom.userFactor = 0.2; + if( G.VOM.zoom.userFactor < G.O.viewerMinZoom ) { + G.VOM.zoom.userFactor = G.O.viewerMinZoom; + return true; } + return false; } @@ -8526,6 +8540,40 @@ $media[0].style[G.CSStransformName] = 'translate(0px, "50%") '; } + // Get Zoom Base Factor (image fill viewer) + function ViewerZoomBaseFactor(item){ + var dpr = 1; + if( G.O.viewerImageDisplay == 'bestImageQuality' ) { + dpr = window.devicePixelRatio; + } + + // retrieve the base zoom factor (image fill screen) + var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr); + var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr); + var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH); + if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) { + // no upscale + zoomBaseFactor = 1; + } + return zoomBaseFactor; + } + + // Get Media Dimensions + function ViewerMediaDimensions(item, isCurrent){ + var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1; + + var dpr = 1; + if( G.O.viewerImageDisplay == 'bestImageQuality' ) { + dpr = window.devicePixelRatio; + } + + var zoomBaseFactor = ViewerZoomBaseFactor(item); + + var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor; + var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor; + return [imageCurrentHeight, imageCurrentWidth]; + } + // Set position and size of ONE media container function ViewerMediaSetPosAndZoomOne(content_item, isCurrent ) { @@ -8544,25 +8592,12 @@ return; } - // part 1: set the image size - var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1; - - var dpr = 1; - if( G.O.viewerImageDisplay == 'bestImageQuality' ) { - dpr = window.devicePixelRatio; - } - - // retrieve the base zoom factor (image fill screen) - var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr); - var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr); - var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH); - if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) { - // no upscale - zoomBaseFactor = 1; - } + // part 0: enforce zoom limits + ViewerZoomMin(); + ViewerZoomMax(); - var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor; - var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor; + // part 1: set the image size + var [imageCurrentHeight, imageCurrentWidth] = ViewerMediaDimensions(item, isCurrent); $img.children().eq(1).css( {'height': imageCurrentHeight }); $img.children().eq(1).css( {'width': imageCurrentWidth }); @@ -8585,7 +8620,7 @@ } G.VOM.zoom.posX = posX; G.VOM.zoom.posY = posY; - ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false); + ViewerImagePanSetPosition(item, G.VOM.panPosX, G.VOM.panPosY, $img, false); } // else { //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) '; @@ -8600,7 +8635,12 @@ // position the image depending on the zoom factor and the pan X/Y position // IMG is the only media kind supporting zoom/pan - function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) { + function ViewerImagePanSetPosition(item, posX, posY, imageContainer, savePosition ) { + // limit panning so that edge of image cannot cross center line of viewer + var [imageCurrentHeight, imageCurrentWidth] = ViewerMediaDimensions(item, true); + posX = Math.max(-(imageCurrentWidth / 2), Math.min(posX, (imageCurrentWidth / 2))); + posY = Math.max(-(imageCurrentHeight / 2), Math.min(posY, (imageCurrentHeight / 2))); + if( savePosition ) { G.VOM.panPosX = posX; G.VOM.panPosY = posY; @@ -8908,7 +8948,7 @@ switch( G.VOM.panMode ) { case 'zoom': // pan zoomed image - ViewerImagePanSetPosition(G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.content.current.$media, false); + ViewerImagePanSetPosition(G.VOM.content.current.NGY2Item(), G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.content.current.$media, false); G.VOM.toolsHide(); break; @@ -8954,7 +8994,7 @@ case 'zoom': // PAN END in image zoom mode G.VOM.timeImgChanged = new Date().getTime(); - ViewerImagePanSetPosition( G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.content.current.$media, true); + ViewerImagePanSetPosition( G.VOM.content.current.NGY2Item(), G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.content.current.$media, true); break; case 'media': var panY = false; @@ -9107,15 +9147,30 @@ ev.srcEvent.stopPropagation(); ev.srcEvent.preventDefault(); // cancel mouseenter event G.VOM.timeImgChanged = new Date().getTime(); + G.VOM.zoom.lastScale = 1; // reset to 1 }); G.VOM.hammertime.on('pinch', function(ev) { ev.srcEvent.stopPropagation(); ev.srcEvent.preventDefault(); // cancel mouseenter event if( ViewerZoomStart() ) { - G.VOM.zoom.userFactor = ev.scale; + if(G.O.viewerRelativePinchZoom){ + var scale = ev.scale; + if (scale < 1){ + scale = 1 - ((1 - scale) * G.O.viewerZoomOutScale); + } + G.VOM.zoom.userFactor = G.VOM.zoom.userFactor + (scale - G.VOM.zoom.lastScale); + G.VOM.zoom.lastScale = scale; + } + else { + G.VOM.zoom.userFactor = ev.scale; + } ViewerZoomMax(); - ViewerZoomMin(); + if (ViewerZoomMin() && G.O.viewerResetZoomAtMin){ + // Reset view + G.VOM.zoom.isZooming = false; + ResizeLightbox(true); + } ViewerMediaSetPosAndZoom(); // center media } });