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

Feature (Epic?): Improve Panzoom for nested content use case (list of components, etc.) #642

Open
5 tasks
nwittwer opened this issue Mar 16, 2023 · 2 comments
Labels
feature votes needed Feature requests are closed at first, but will be implemented with enough upvotes

Comments

@nwittwer
Copy link

What problem does this feature solve?

High-level idea: It would be really cool to be able to use Panzoom to enable an interactive "infinite canvas" like Figma/Miro, and use the Panzoom library to pan/zoom around the content. Same use case as was discussed in #472.

The content on said "canvas" should be assumed it will be dynamic, for example binded to a reactive Vue/React array of objects ([{id:1}, {id:2}, {id:3}]. When the content changes, Panzoom should re-calculate its initial/reset coordinates based on the new width/height of the contents container. It should be easy to call a method which places the content in the center of the screen, and zooms out so that all the content is visible. It should be easy call a method which pans + zooms to a given DOM Element, so that element is in the center of the screen. Focal zooming and pan() and zoom() should still work, but be aware of the contents position instead of just the "Panzoom Child" (aka Panzoom instance) element.

Here's an example of the intended interactions:

Example interaction

--

Panzoom library consumers can already enable moving the content around like shown above using the canvas option (#472, Docs), which is a really helpful start!

HTML

<!-- The parent of the instance (receives `canvas` option EventListeners)  -->
<div id="panzoom-parent">
  <!-- The Panzoom instance element  -->
  <div id="panzoom-child">
    <!-- The pan/zoom-able contents within Panzoom instance  -->
    <div id="panzoom-contents"></div>
  </div>
</div>

JS

Panzoom(document.querySelector("#panzoom-child"), {
  canvas: true, // Use the `canvas` option
}

With just this code^, the result looks like this:
Screen Recording 2023-03-16 at 10 09 25 AM

Please see the Problem demo for the code.

The most obvious things missing to achieve the goal mentioned above are:

  1. Panzoom does not take the height/width of the content inside "Panzoom Child" (instance element) into consideration by default, so it currently always displays the contents in the top-left corner and is not zoomed out enough to see all the content. (See problem demo link^)
    • Workaround: Possible to manually use the pan() and zoom(), but the calculations are not straightforward, because position is not calculated based on the contents.
  2. Panzoom doesn't automatically set the scale so that the contents fit within the viewport (on startup/initialization): Feature: set initial scale of the Panzoom element to always fit viewport #616.
  3. Would also be useful if panzoom.reset() method could also keep track of what the correct x/y and scale would be so that the content would fit within the viewport. The options for startX, startY, startScale can be set manually, but would be great if the calculations were handled automatically.
  4. Panzoom is using transform-origin: 50% 50%, but the x:0, y:0 coordinates become confusing quickly because when using the canvas option and when the "Panzoom Contents" are dynamic and using styles like display: inline-block or flex or grid.
  5. Zooming and focal is not easily adjusted to the dynamic contents, instead of the "Panzoom Child" (instance) element
  6. Unclear how to pan/zoom in to a specific DOM Element (e.g. one of the "items") and have it visually in the center of the container. zoomToPoint() exists... but I wasn't able to figure out how to properly calculate the clientX and clientY values to pass to it in this case.
  7. (Nice-to-have) Some more options to help keep the user from moving way outside of the container and getting lost.
    1. Option 1: Add another option to contain
    2. Option 2: Add something like a maxPanRatio

It's not clear to what extent these features could/should be handled by Panzoom library or should be handled by consumers, so I definitely understand if not all of this makes sense in the scope of the library.

Describe the solution you'd like

Here's an attempt at some specific changes to Panzoom which would enable some of the needs, and some idea for how to name things, but probably it needs more iteration/feedback.

  • Add content option: override which DOM Element Panzoom will use for calculations of x, y and scale, taking into account the height/width of the element.
    • Default to the Panzoom instance element
    • Allow consumers to specify a different DOM Element (like #panzoom-contents). Focal zooming, panning, etc. should then be relative to this element.
    • Automatically watch for changes to the content DOM Element's height/width and update the Panzoom instance accordingly, to avoid jankiness with Panzoom and coordinates? Perhaps with resizeObserver?
    • Watch the CSS styles for the content DOM Element and adjust the calculations depending on if the element is block or inline? Something similar mentioned here: Focal zoom with flex-centered images #611 (comment)
  • Add 'auto' option to startX, startY, startScale to let Panzoom automatically calculate the appropriate x/y/scale which fits the content within the container when the instance is initialized and reset()
  • Add zoomPadding: 0.25 to specify how much padding (%) to add between the contents and the container when auto-calculating the scale (startScale: 'auto')
  • Add zoomToElement() method to zoom in to a specific DOM Element which exists within the Panzoom element, and let Panzoom visually center it within the container. This can also factor in the option zoomPadding.
  • Add another option to contain or something like maxPanRatio to help keep the user from moving way outside of the container and getting lost. Maybe contain: 'content', if we have this content concept? (Relates to Feature: New contain option for both inside/outside #598, Feature: 3rd contain option (mix of both inside and outside) #504, Feature: contain: 'outside', but allow the image to fit the container? #577, Feature: Limit pan distance outside of canvas, e.g. maxPanRatio #476)

Example:

<!-- (receives `canvas` option EventListeners)  -->
<div id="panzoom-parent">
  <!-- The Panzoom instance element  -->
  <div id="panzoom-child">
    <div id="panzoom-contents"><!-- Contents within Panzoom  --></div>
  </div>
</div>
const element = document.querySelector('#panzoom-child')

const instance = Panzoom(element, {
  canvas: true,
  content: document.querySelector('#panzoom-contents'),
  startX: 'auto',
  startY: 'auto',
  startScale: 'auto',
  zoomPadding: 0.25
}

instance.zoomToElement(document.querySelector('#item-1'))

Describe alternatives you've considered

Of course, all these features could somehow be handled somehow by Panzoom library consumers. But it would be awesome to not have to hack it in a way that it wasn't meant to be used.

Alternative 1: Changing transform-origin to 0 0 instead of default 50% 50% to make it easier to work with the x/y coordinate system

  • This makes it a bit more clear how to work with 0, 0 coordinate system, but it makes other things like adjusting the zoom to work again difficult.
    • i.e. library consumer has to correct the focal point or zoomToPoint() for scroll zooming (e.g. scroll wheel zoom in/out towards mouse position)
    • There was this example given: https://jsfiddle.net/timmywil/sdok6rjp/1/. If you just click "zoom" it's clear; but after clicking "pan" and then "zoom" it is unintuitive.
    • I noticed your explanation of focal and suggestion of using zoomToPoint():
      • focal is a low-level option for focusing in on a point of the element, not relative to the parent dimensions. But zooming the way we usually think about it is often relative to parent dimensions and not the element, which is why Panzoom has the zoomToPoint method to make things easier. (via Focal point not very consistent and robust #564 (comment))

    • Suggestion to use zoomToPoint: help with zoomToPoint #521 (comment)
      • zoomToPoint allows the user to zoom in on any point in the parent element. In your case, the body. This is so things won't bug out when the Panzoom element is smaller than the parent, as is often the case. zoomToPoint uses the focal option internally, but is a convenience method to handle some of the complex conversions that often accompany zooming. In this case, converting a mouseover's clientX and clientY to a point inside the parent on which to zoom. [...]That is, you're not zooming to a point on the SVG, but a point somewhere in the parent, that then gets converted to a point on the SVG.

    • However zoomToPoint() it is also unclear how to calculate the clientX and clientY for the goals described.
    • Another option mentioned was to set the focal point to the parent offset (https://jsfiddle.net/timmywil/sdok6rjp/2/). Perhaps this would work but it seems like the "zoom" only makes sense if you don't first click "pan"; otherwise the result is unexpected.
    • Usually the results end up like this: https://codepen.io/erinxi/pen/WNXzovm (example from Focal zoom with flex-centered images #611)
@nwittwer nwittwer changed the title Feature (Epic?): Improve Panzoom for dynamic nested content use case (components, etc.) Feature (Epic?): Improve Panzoom for nested content use case (list of components, etc.) Mar 16, 2023
@timmywil
Copy link
Owner

Thanks for your incredibly well articulated feature request! There are a lot of good suggestions here. Making Panzoom smarter, especially in the case of canvas is something I've always wanted to do, but when I've tried it in the past, the resulting code is much larger than I'm comfortable with. Panzoom is meant to be small, but the trade off is that it is low-level and not always smart in every case. I think it's great for what I consider to be the most common cases.

That said, the time you've put into this ticket alone is a strong argument for reconsidering my position. This may be a good time for a rewrite, especially considering Panzoom was originally built with support for IE (which, by the way, is why I couldn't change transform-origin to 0 0). It literally won't let you for certain elements.

I'm going to leave this open and add votes needed. Normally, I would close it to add it to the feature votes pipeline, but I'm willing to bet there will be more tickets open that I could close as dupes of this one.

@timmywil timmywil added the votes needed Feature requests are closed at first, but will be implemented with enough upvotes label Mar 16, 2023
@nwittwer
Copy link
Author

Thanks for the consideration and sharing your thoughts, @timmywil!

That sounds like a great direction. Would be really cool to see what an even better, smarter, modern (non-IE) version of Panzoom would look like!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature votes needed Feature requests are closed at first, but will be implemented with enough upvotes
Projects
None yet
Development

No branches or pull requests

2 participants