Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

work in progress about viewer resize behavior #2256

Merged
merged 8 commits into from
Dec 16, 2022

Conversation

pearcetm
Copy link
Contributor

@pearcetm pearcetm commented Dec 7, 2022

In one of the apps I'm working on, I noticed some strange and sometimes inconsistent behavior of a viewer when it's size was changed at the same time that it switched to a new tilesource. Tracking down the root of this has sent me down a bit of a rabbit hole about how viewer resizing is handled.

This PR is currently in draft mode, and the code includes some changes that are intended just for comparative testing of before/after behavior, which would need to be removed if any of this moves forward towards merging.

Three distinct issues/proposals are presented here for consideration.

  1. Separating the logic of (auto) resizing of the viewer into a new private function, and calling this using either an auto-update mechanism or a new API function forceResize().
  2. Switching from the current polling of element size to using a ResizeObserver (where supported).
  3. Updating the logic of what actually happens to the viewport during a resize.

I have created a new demo which is available at http://localhost:8000/test/demo/resizeviewer.html when running the development environment, which allows testing viewer behavior with various combinations of parameters/code versions. I hope the demo page is largely self-explanatory, but I expect there will be plenty of questions and discussion around this.

@pearcetm pearcetm marked this pull request as draft December 7, 2022 23:04
Copy link
Member

@iangilman iangilman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I had no idea that ResizeObserver existed. Looks like it's pretty well covered by our target browsers, but I support continuing to have the fallback. But yeah, using ResizeObserver will be a nice efficiency improvement!

Thank you for the demo page... It's a lot easier to understand what's going on that way. My observations:

With the 3.1 technique and preserve image size off, it seems the width and height resizes both resize the image down, but then don't resize it back up. That seems like a bug in the existing code.

With the new technique and preserve image size off, the width one resizes the image down and back up properly. The height one, however, doesn't resize the image down. That seems like a bug in the new code.

I can't get the bottom demo to produce the problem you describe. I have "use pulling" on and "preserve" off, and I hit the next/previous page buttons, and everything looks fine. Sounds like it's an intermittent error that only happens sometimes?

Is your proposed fix that the developer needs to add a forceResize call in their own code, or are you suggesting that we add that to the Viewer so it happens automatically? Ideally, it shouldn't require extra work for the developer.

src/viewer.js Outdated Show resolved Hide resolved
src/viewer.js Outdated Show resolved Hide resolved
src/viewer.js Outdated Show resolved Hide resolved
src/viewer.js Outdated Show resolved Hide resolved
@pearcetm
Copy link
Contributor Author

pearcetm commented Dec 9, 2022

@iangilman Thanks for reviewing!

Cool! I had no idea that ResizeObserver existed. Looks like it's pretty well covered by our target browsers, but I support continuing to have the fallback. But yeah, using ResizeObserver will be a nice efficiency improvement!

The only potential sticking point I could see in terms of a change in behavior would be that if a resize happens during _opening the ResizeObserver would be triggered, while the existing polling method would not be activated until later, because of the guard at the beginning of updateOnce:

    if (viewer._opening || !THIS[viewer.hash]) {
        return;
    }

With the 3.1 technique and preserve image size off, it seems the width and height resizes both resize the image down, but then don't resize it back up. That seems like a bug in the existing code.

With the new technique and preserve image size off, the width one resizes the image down and back up properly. The height one, however, doesn't resize the image down. That seems like a bug in the new code.

The existing behavior of the viewer defines the scale/zoom of the image based on width, which is why in the "new code" resizing only the height doesn't rescale the image. So, I don't think it's a "bug," but whether this is actually desirable or not, I'm not sure. I share your feeling that it isn't necessarily intuitive... but it seems like this has been a longstanding "feature" of the system, so I worry that changing this might break things some how.

I can't get the bottom demo to produce the problem you describe. I have "use pulling" on and "preserve" off, and I hit the next/previous page buttons, and everything looks fine. Sounds like it's an intermittent error that only happens sometimes?

To clarify what the problematic behavior looks like, when I page right/forward, which causes a narrower viewer element because of a wider sidebar than the previous image, the images start out less than full height of the viewer. (Actually, they really do start at full height because of goHome(), but then the viewer width narrows and they shrink.) In contrast, paging to the left shrinks the sidebar/widens the viewer, so the images start out at full height (as expected).

If you aren't seeing this behavior that is interesting. I can provide a video if that would be useful.

In my testing, using the ResizeObserver instead of waiting for polling does help sometimes, but the behavior actually becomes more inconsistent. I think it has to do with exactly when the browser decides to trigger the layout change, and when that happens relative to when goHome() is called during opening.

Is your proposed fix that the developer needs to add a forceResize call in their own code, or are you suggesting that we add that to the Viewer so it happens automatically? Ideally, it shouldn't require extra work for the developer.

Calling forceResize or whatever we end up naming it seems to fix the above issue by asking the viewer to access the element height/width properties, which then forces the browser to update the layout immediately, and results in the viewer being immediately set to the new size. Unfortunately, I can't see how to make the viewer detect this reliably in an automatic fashion (both the current polling and the ResizeObserver method have their own shortcomings). Manually calling this right after updating the DOM/CSS at least provides an option for developers to use to force the resize to happen immediately. This is why I had added the argument about respecting or overriding the autoResize property - thinking that it would be potentially easier for developers to call this (with the flag) after any layout change to give the viewer the chance to resize if autoResize=true and ignoreAutoResizeSetting=false, or else truly force it to resize with ignoreAutoResizeSetting=true.

@iangilman
Copy link
Member

I updated the name to forceImmediateResize, to emphasize why it is (hypothetically) useful.

I think I like forceResize better... forceImmediateResize implies to me that it'll resize later on its own, which I don't think is the case.

The only potential sticking point I could see in terms of a change in behavior would be that if a resize happens during _opening the ResizeObserver would be triggered, while the existing polling method would not be activated until later, because of the guard at the beginning of updateOnce

How about we always do the resize in updateOnce, and the ResizeObserver handler just sets a needsResize flag? We can have the forceResize work the same way. This would mirror forceRedraw but also allow us to consolidate things like the _opening logic.

The existing behavior of the viewer defines the scale/zoom of the image based on width, which is why in the "new code" resizing only the height doesn't rescale the image. So, I don't think it's a "bug," but whether this is actually desirable or not, I'm not sure. I share your feeling that it isn't necessarily intuitive... but it seems like this has been a longstanding "feature" of the system, so I worry that changing this might break things some how.

True. In fact, I suppose the reason the image stays the same height in your issue demo when you hit the left arrow is because of this. I do think it would be an improvement though, to make it consistent horizontally and vertically, so I think it's worth making the change if we can.

To clarify what the problematic behavior looks like, when I page right/forward, which causes a narrower viewer element because of a wider sidebar than the previous image, the images start out less than full height of the viewer. (Actually, they really do start at full height because of goHome(), but then the viewer width narrows and they shrink.) In contrast, paging to the left shrinks the sidebar/widens the viewer, so the images start out at full height (as expected).

I see! I suppose that was happening before and I just didn't know what to look for. I am definitely seeing it now.

This seems like a specific enough scenario that I don't think I would want OSD to try to automatically account for it itself. I think if I was the developer of an app that needed this, I would add the sidebar in and put goHome in a short setTimeout.

@pearcetm
Copy link
Contributor Author

pearcetm commented Dec 10, 2022

I think I like forceResize better... forceImmediateResize implies to me that it'll resize later on its own, which I don't think is the case.

I can get on board with forceResize, especially if we don't intend it as a mechanism to force the timing of the resize to happen before the next updateOnce loop.

How about we always do the resize in updateOnce, and the ResizeObserver handler just sets a needsResize flag? We can have the forceResize work the same way. This would mirror forceRedraw but also allow us to consolidate things like the _opening logic.

This seems like a good idea to me. I had pulled it out of updateOnce hoping that the resize could happen immediately on altering the layout when watched by ResizeObserver but since that's not the case, may as well put it back there again.

True. In fact, I suppose the reason the image stays the same height in your issue demo when you hit the left arrow is because of this. I do think it would be an improvement though, to make it consistent horizontally and vertically, so I think it's worth making the change if we can.

I'm happy to work on implementing something, but it's not obvious what the right answer is to me just yet. I think it would be useful to put the specific behavior you have in mind into words. How would you propose handling width-only, height-only, and both-but-changed-aspect-ratio cases, while shrinking and growing?

I see! I suppose that was happening before and I just didn't know what to look for. I am definitely seeing it now.

That makes sense. In the demo it's less obvious than in my actual use-case, but I wanted both the viewer and controls to have enough space to be usable.

This seems like a specific enough scenario that I don't think I would want OSD to try to automatically account for it itself. I think if I was the developer of an app that needed this, I would add the sidebar in and put goHome in a short setTimeout.

Having gone through the trouble of trying to automate it to some degree, I agree that we probably can just get by with setTimeout because I don't think there's a great way to do it truly automatically. Depending on how the resize behavior is changed as we are discussing above, it may actually just make the whole problem go away. setTimeout brings its own set of potential problems, but you're right that it is an option that could be used.

@iangilman
Copy link
Member

I'm happy to work on implementing something, but it's not obvious what the right answer is to me just yet. I think it would be useful to put the specific behavior you have in mind into words. How would you propose handling width-only, height-only, and both-but-changed-aspect-ratio cases, while shrinking and growing?

I'm thinking in all of those cases when the viewer shrinks, so does the image, and when the viewer grows again, so does the image. Currently, when the width changes, it already does that, but when only the height changes, it does not.

Furthermore, and this is the tricky part: If, after it shrinks, you grow the viewer back to its original size/shape, the image should be exactly the same size it was before the shrink/grow.

I guess the real question is "by how much should it shrink/grow?" Right now, it's scaling down the image proportional to how much the viewport width is scaling down. Maybe it should look at the proportion the viewport height is scaling, and if it's scaling down more, use that to scale down the image? The challenge, of course, is that the image maintains its aspect ratio, whereas the viewport can change its aspect ratio.

Actually, maybe it would be better to scale it down by the lesser of the two scales? That way it's not as disruptive. You might end up with something chopped off, but it's a zooming system, so maybe that's okay?

Then, in terms of scaling back up, I guess it's whatever reverses the process properly.

What do you think? BTW, we don't have to go down this rabbit hole if you don't want to... This is just where we've gotten to, starting from your new technique :)

@pearcetm
Copy link
Contributor Author

@iangilman I updated the PR with a proposed modification to the scaling behavior. I ended up going with a scale factor based on the diagonal of the viewer, which I think looks and feels nice and intuitive, and ends up with the same view when returned to the same viewer dimensions (barring any actual navigation of course). I added a demo mode with a manually resizable viewer (the new bottom button). It's a little hard to see the jquery-ui resizable handle, but when this is activated, you can grab the bottom right corner of the viewer and drag it around.

I also implemented the suggestion to just set a flag for needsResize and made the other changes that you suggested.

Thoughts on next steps? (I'm not sure why the build is failing)

Copy link
Member

@iangilman iangilman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful! Of course, the diagonal is the right choice! It just hadn't occurred to me.

The new resizing feels really nice. Thank you for providing the free resizing demo.

I'd say this is all good, but it still needs some cleanup. We absolutely should keep this new demo page, but I think we should remove the old versus new toggle and all the code that supports that.

The failing build is:

https://app.travis-ci.com/github/openseadragon/openseadragon/builds/258833878

Can you see that, or do you not have access?

At any rate, it looks like it's the navigator tests that are failing. Can you merge in the latest from master, and see if that fixes it?

src/viewer.js Outdated Show resolved Hide resolved
src/viewer.js Outdated Show resolved Hide resolved
src/viewer.js Outdated Show resolved Hide resolved
src/viewer.js Outdated Show resolved Hide resolved
@pearcetm
Copy link
Contributor Author

@iangilman The tests were actually useful! It turns out the new resize behavior had changed how the navigator was working. The simple fix is just to disable autoResize in the $.Navigator constructor instead of inheriting that option from the parent viewer. _resizeWithViewer is supposed to handle that logic internally within navigator anyway, so it probably didn't make sense to also have autoResize triggering anything. I guess previously it just happened to not hurt anything...

Copy link
Member

@iangilman iangilman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point on the navigator! Probably more efficient this way too :)

It's looking great. Just a small refinement suggestion. Also, it's time to take it out of draft mode!

var oldBounds = viewport.getBounds();
viewport.resize(containerSize, true);
viewport.fitBoundsWithConstraints(oldBounds, true);
if (viewer.autoResize || THIS[viewer.hash].forceResize){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like we need forceResize... This can just be a check for needsResize (along with the autoResize check, of course).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the point of forceResize (as it currently stands, at least) is to allow a developer a way to trigger (force) the resize behavior even if autoResize==false. That's why I added a second flag, because otherwise if autoResize==false then needsResize is ignored.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the behavior we want. Now that you mention it, if you take my suggestion and get rid of the forceResize flag, you would want to check for autoResize in the ResizeObserver callback.

I guess the key difference would be what would happen if you had autoResize off, resized the viewer, then turned it back on. With the separate forceResize flag, it could remember that it got resized and therefore immediately update when autoResize got turned back on. By using needsResize for the forceResize functionality, in that scenario it wouldn't remember that it needed to resize, and therefore it wouldn't resize when you turned autoResize on again.

I mentioned it because it seemed like a little bit of a pain to have to manage both of those flags, but now I think I've talked myself into wanting them both.

What do you think? If you agree, then I think we're good here and I can merge it. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I think keeping the two flags for managing two different behaviors/usages makes sense. Since they're internal to Viewer and used for pretty straightforward purposes, I don't think it should be too painful to keep up with the logic of what's going on for future development. Also, I think your description of the behavior with the two flags - that when autoResize is turned on, it should resize immediately if needed - makes good sense for how it should be expected to work.

So, I'm good with moving forward with merging as long as you're satisfied with everything!

@pearcetm pearcetm marked this pull request as ready for review December 14, 2022 23:17
Copy link
Member

@iangilman iangilman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent. Let's merge it! Thank you for digging in and cleaning this up!

@iangilman iangilman merged commit 1351ac0 into openseadragon:master Dec 16, 2022
iangilman added a commit that referenced this pull request Dec 16, 2022
msalsbery added a commit that referenced this pull request Feb 1, 2023
* master: (276 commits)
  Changelog for #2280 and #2238
  remove trailing space
  fix problem with click precision on ReferenceStrip
  Changelog for #2273
  Also add documentation for tileRetryDelay
  try fix with check for null and undefined
  fix build error
  Add tileRetryMax documentation.
  Revert async support and event breaking support in EventSource.
  Changelog for #2276
  add box-sizing property to the navigator display region
  Implement support for async function and promise type recognition with $.type. Add $.Promise proxy. Implement support for promises in EventSource. Implement ability to abort events as well as prioritize events.
  Changelog for #2270
  issues/2192 fix.
  Starting 4.0.1
  Version 4.0.0
  JSDoc fixes
  Changelog for #2256
  Changelog for #2249
  removed polling vs resizeviewer option from demo
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants