diff --git a/src/viewer.js b/src/viewer.js index 748137fe..4dbec3c7 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -328,6 +328,22 @@ $.Viewer = function( options ) { THIS[ this.hash ].prevContainerSize = _getSafeElemSize( this.container ); + this._onViewerResize = onViewerResize; + this._origViewerResize = origViewerResize; //for testing logic changes + if(ResizeObserver){ + this._autoResizePolling = false; + + this._resizeObserver = new ResizeObserver(function(){ + if(_this.autoResize){ + _this._onViewerResize(_this, _getSafeElemSize(_this.container)); + } + }); + + this._resizeObserver.observe(this.container, {}); + } else { + this._autoResizePolling = true; + } + // Create the world this.world = new $.World({ viewer: this @@ -786,6 +802,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, //TODO: implement this... //this.unbindSequenceControls() //this.unbindStandardControls() + if (this._resizeObserver){ + this._resizeObserver.disconnect(); + } if (this.referenceStrip) { this.referenceStrip.destroy(); @@ -1680,6 +1699,18 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, return this; }, + /** + * Force the viewer to reset it's size to it's container. + * @param Boolean ignoreAutorResizeSetting (default: false) If true, forces the resize even if Viewer.autoResize==false + * @returns {OpenSeadragon.Viewer} Chainable. + */ + forceResize: function(ignoreAutoResizeSetting) { + if(ignoreAutoResizeSetting || this.autoResize){ + this._onViewerResize(this, _getSafeElemSize(this.container)); + } + return this; + }, + /** * @function * @returns {OpenSeadragon.Viewer} Chainable. @@ -3547,7 +3578,40 @@ function updateMulti( viewer ) { viewer._updateRequestId = false; } } +function origViewerResize(viewer, containerSize){ + var viewport = viewer.viewport; + if (viewer.preserveImageSizeOnResize) { + var resizeRatio = THIS[viewer.hash].prevContainerSize.x / containerSize.x; + var zoom = viewport.getZoom() * resizeRatio; + var center = viewport.getCenter(); + viewport.resize(containerSize, false); + viewport.zoomTo(zoom, null, true); + viewport.panTo(center, true); + } else { + // maintain image position + var oldBounds = viewport.getBounds(); + viewport.resize(containerSize, true); + viewport.fitBoundsWithConstraints(oldBounds, true); + } + THIS[viewer.hash].prevContainerSize = containerSize; + THIS[viewer.hash].forceRedraw = true; +} +function onViewerResize(viewer, containerSize){ + var viewport = viewer.viewport; + var resizeRatio = THIS[viewer.hash].prevContainerSize.x / containerSize.x; + var zoom = viewport.getZoom(); + var center = viewport.getCenter(); + viewport.resize(containerSize, viewer.preserveImageSizeOnResize); + viewport.panTo(center, true); + if (viewer.preserveImageSizeOnResize) { + viewport.zoomTo(zoom * resizeRatio, null, true); + } else { + viewport.zoomTo(zoom, null, true); + } + THIS[viewer.hash].prevContainerSize = containerSize; + THIS[viewer.hash].forceRedraw = true; +} function updateOnce( viewer ) { //viewer.profiler.beginUpdate(); @@ -3555,27 +3619,11 @@ function updateOnce( viewer ) { if (viewer._opening || !THIS[viewer.hash]) { return; } - - if (viewer.autoResize) { + if (viewer.autoResize && viewer._autoResizePolling){ var containerSize = _getSafeElemSize(viewer.container); var prevContainerSize = THIS[viewer.hash].prevContainerSize; if (!containerSize.equals(prevContainerSize)) { - var viewport = viewer.viewport; - if (viewer.preserveImageSizeOnResize) { - var resizeRatio = prevContainerSize.x / containerSize.x; - var zoom = viewport.getZoom() * resizeRatio; - var center = viewport.getCenter(); - viewport.resize(containerSize, false); - viewport.zoomTo(zoom, null, true); - viewport.panTo(center, true); - } else { - // maintain image position - var oldBounds = viewport.getBounds(); - viewport.resize(containerSize, true); - viewport.fitBoundsWithConstraints(oldBounds, true); - } - THIS[viewer.hash].prevContainerSize = containerSize; - THIS[viewer.hash].forceRedraw = true; + viewer._onViewerResize(viewer, containerSize); } } diff --git a/test/demo/resizeviewer.html b/test/demo/resizeviewer.html new file mode 100644 index 00000000..9341feb7 --- /dev/null +++ b/test/demo/resizeviewer.html @@ -0,0 +1,340 @@ +<!DOCTYPE html> +<html> +<head> + <title>OpenSeadragon Viewer Resizing Demo</title> + <script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script> + <script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script> + <style type="text/css"> + + .outer-container{ + width:800px; + height:600px; + margin-right:20px; + border: medium gray dashed; + background-color:beige; + position:relative; + display:flex; + flex-direction:row; + } + .inner-container{ + width:800px; + height:600px; + border: thin black solid; + background-color:paleturquoise; + position:absolute; + left:50%; + top: 50%; + transform: translate(-50%, -50%); + } + .v2-container{ + display:flex; + flex-direction:row; + height:400px; + } + .narrow{ + width:50px; + background-color:pink; + height:100%; + } + .wide{ + width:150px; + background-color:burlywood; + height:100%; + } + .v2-container:not([data-index="1"]) .narrow{ + display:none; + } + .v2-container:not([data-index="2"]) .wide{ + display:none; + } + #viewer, #viewer2 { + width: 100%; + height: 100%; + } + #buttons button{ + width:18em; + text-align:center; + margin:5px; + } + .layout{ + display:grid; + grid-template-columns:auto 1fr; + row-gap:10px; + padding:10px; + } + .method{ + border:medium gray solid; + margin:2px; + background-color:rgb(240, 240, 240) + } + .method.selected{ + border:medium red solid; + background-color: lightgoldenrodyellow; + } + .options{ + display:grid; + grid-template-columns: 50% 50%; + row-gap:10px; + } + </style> +</head> +<body> + <div class="layout"> + <div class="outer-container"> + <div class="inner-container"> + <div id="viewer"></div> + </div> + </div> + <div id="controls"> + <div> + Simple demo page to show viewer behavior during resizing of the container. + The viewers' container elements are styled with width and height of 100%, + with dimensions set by CSS properties on a parent element. + </div> + + <h3>Pick options to test:</h3> + <p>These options apply to both of the demo viewers on the left (top and bottom).</p> + <div class="options"> + <div class="method logic-method selected" data-value="0"> + <pre>Use v3.1 resize logic</pre> + </div> + <div class="method logic-method" data-value="1"> + <pre>Use new resize logic</pre> + </div> + + <div class="method listener-method selected" data-value="0"> + <pre>Use polling</pre> + </div> + <div class="method listener-method" data-value="1"> + <pre>Use ResizeObserver</pre> + </div> + + <div class="method preserve-method selected" data-value="0"> + <pre>preserveImageSizeOnResize: false</pre> + </div> + <div class="method preserve-method" data-value="1"> + <pre>preserveImageSizeOnResize: true</pre> + </div> + + <div class="method auto-method selected" data-value="1"> + <pre>autoResize: true</pre> + </div> + <div class="method auto-method" data-value="0"> + <pre>autoResize: false</pre> + </div> + + </div> + <h3>Click to resize the viewer:</h3> + <div id="buttons"> + <div> + <button>Resize width only</button> + </div> + <div> + <button>Resize height only</button> + </div> + <div> + <button>Resize with constant aspect ratio</button> + </div> + </div> + + </div> + <div> + <div class="outer-container v2-container"> + <div id="viewer2"></div> + <div class="sidebar"> + <div class="wide">Wide sidebar</div> + <div class="narrow">Narrow sidebar</div> + </div> + + </div> + + </div> + <div> + <h3>Demo: Issues with styling the viewer element when an image is opened</h3> + <p>If a DOM/CSS operation is triggered by a viewer event (e.g. the 'page') event + for multi-page images, the timing of the viewer resizing itself (if autoResize=true) + can be inconsistent - sometimes before, sometimes after the image has finished loading + and the Viewport.goHome() call is generated. To ensure consistency, call viewer.forceResize() + after altering the page layout. + </p> + <p>Note: to see this in action most clearly, select the options for "preserveImageSizeOnResize: false" + and "Use polling" above. + </p> + <div class="options"> + <div class="method force-resize-method selected" data-value="0"> + <pre>//pseudocode +//container will change size +setSidebarForImage(); + +// image might not "open" at home +// viewer.forceResize(); + </pre> + </div> + <div class="method force-resize-method" data-value="1"> + <pre>//pseudocode +//container will change size +setSidebarForImage(); + +//image should always "open" at home +viewer.forceResize(); + </pre> + </div> + </div> + </div> + </div> + + + <script type="text/javascript"> + + var viewer = window.v1 = OpenSeadragon({ + id: "viewer", + prefixUrl: "../../build/openseadragon/images/", + tileSources: "../data/iiif_2_0_sizes/info.json", + minZoomImageRatio: 0, + maxZoomPixelRatio: 10, + visibilityRatio:1.0, + constrainDuringPan:true, + }); + + + var viewer2 = window.v2 = OpenSeadragon({ + id: "viewer2", + prefixUrl: "../../build/openseadragon/images/", + tileSources: ["../data/testpattern.dzi", "../data/iiif_2_0_sizes/info.json", "../data/iiif_3_0_tiled/info.json"], + sequenceMode:true, + minZoomImageRatio: 0, + maxZoomPixelRatio: 10, + visibilityRatio:1.0, + constrainDuringPan:true, + }); + viewer2._tilesource = "../data/iiif_2_0_sizes/info.json"; + + + var _onViewerResize = viewer._onViewerResize; + var _origViewerResize = viewer._origViewerResize; + + updateViewers(); + + + + + + + var actions = ['Resize width only', 'Resize height only', 'Resize with constant aspect ratio'] + + + var buttons=$('#buttons button').on('click',function(){ + switch($(this).text()){ + case 'Resize width only': resizeWidth(this); break; + case 'Resize height only': resizeHeight(this); break; + case 'Resize with constant aspect ratio': resizeBoth(this); break; + } + }); + + + // $('#update-viewer2').on('click',function(){ + // // updateViewer2(); + // }) + function updateViewers(){ + viewer2._onViewerResize = viewer._onViewerResize = !!parseInt($('.logic-method.selected').data('value')) ? _onViewerResize : _origViewerResize; + + viewer2.preserveImageSizeOnResize = viewer.preserveImageSizeOnResize = !!parseInt($('.preserve-method.selected').data('value')); + + viewer2.autoResize = viewer.autoResize = !!parseInt($('.auto-method.selected').data('value')); + + var useResizeObserver=!!parseInt($('.listener-method.selected').data('value')); + if(useResizeObserver){ + if(viewer._resizeObserver && viewer._autoResizePolling){ + viewer._resizeObserver.observe(viewer.container); + viewer2._resizeObserver.observe(viewer2.container); + viewer2._autoResizePolling = viewer._autoResizePolling = false; + } + } else { + if(viewer._resizeObserver && !viewer._autoResizePolling){ + viewer._resizeObserver.observe(viewer.container); + viewer2._resizeObserver.unobserve(viewer2.container); + viewer2._autoResizePolling = viewer._autoResizePolling = true; + } + } + + } + let pageEvent = 0; + viewer2.addHandler("page", function(event) { + $('.v2-container').attr('data-index',''+event.page); //this can trigger a resize of the element via css + if(!!parseInt($('.force-resize-method.selected').data('value'))){ + viewer2.forceResize(); + } + // console.log('Page change #'+ ++pageEvent +' - going home'); + // setTimeout(()=>viewer2.viewport.goHome(true)); + }); + + $('.logic-method').on('click',function(){ + $('.logic-method').removeClass('selected'); + $(this).addClass('selected'); + }); + + $('.preserve-method').on('click',function(){ + $('.preserve-method').removeClass('selected'); + $(this).addClass('selected'); + }); + + $('.auto-method').on('click',function(){ + $('.auto-method').removeClass('selected'); + $(this).addClass('selected'); + }); + + $('.force-resize-method').on('click',function(){ + $('.force-resize-method').removeClass('selected'); + $(this).addClass('selected'); + }); + + if(!window.ResizeObserver){ + $('.listener-method[data-value=1]').text('ResizeObserver not support in your browser').addClass('invalid'); + } + $('.listener-method').on('click',function(){ + $('.listener-method').removeClass('selected'); + if(!$(this).hasClass('invalid')) $(this).addClass('selected'); + }) + + $('.method').on('click',updateViewers); + + var container = $('.inner-container'); + function resizeWidth(b){ + if(container.height() !== 600) return; + if(container.width()==800){ + container.width(600); + $('#buttons button').prop('disabled', true); + $(b).prop('disabled', false); + } else { + container.width(800); + $('#buttons button').prop('disabled', false); + } + } + function resizeHeight(b){ + if(container.width() !== 800) return; + if(container.height()==600){ + container.height(450); + $('#buttons button').prop('disabled', true); + $(b).prop('disabled', false); + } else { + container.height(600); + $('#buttons button').prop('disabled', false); + } + } + function resizeBoth(b){ + if(container.height()==600){ + container.width(600); + container.height(450); + $('#buttons button').prop('disabled', true); + $(b).prop('disabled', false); + } else { + container.width(800); + container.height(600); + $('#buttons button').prop('disabled', false); + } + } + + + </script> +</body> +</html> \ No newline at end of file