panzoom
panzoom copied to clipboard
Feature (Epic?): Improve Panzoom for nested content use case (list of components, etc.)
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:
--
Panzoom
library consumers can already enable moving the content around like shown above using the canvas
option (https://github.com/timmywil/panzoom/issues/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:
Please see the Problem demo for the code.
The most obvious things missing to achieve the goal mentioned above are:
- 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()
andzoom()
, but the calculations are not straightforward, because position is not calculated based on the contents.
- Workaround: Possible to manually use the
- Panzoom doesn't automatically set the scale so that the contents fit within the viewport (on startup/initialization): https://github.com/timmywil/panzoom/issues/616.
- 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 forstartX
,startY
,startScale
can be set manually, but would be great if the calculations were handled automatically. - Panzoom is using
transform-origin: 50% 50%
, but the x:0, y:0 coordinates become confusing quickly because when using thecanvas
option and when the "Panzoom Contents" are dynamic and using styles likedisplay: inline-block
orflex
orgrid
.- My confusion is similar to the issue mentioned here: https://github.com/timmywil/panzoom/issues/273,
- Working with the x/y coordinate system is confusing once the "Panzoom Child" has height/width that changes. Consumers have to set the x values to negative in order for them to move to the right of the screen.
- Zooming and
focal
is not easily adjusted to the dynamic contents, instead of the "Panzoom Child" (instance) element- Related: https://github.com/timmywil/panzoom/issues/635
- 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 theclientX
andclientY
values to pass to it in this case. - (Nice-to-have) Some more options to help keep the user from moving way outside of the container and getting lost.
- Option 1: Add another option to
contain
- https://github.com/timmywil/panzoom/issues/598
- https://github.com/timmywil/panzoom/issues/504
- https://github.com/timmywil/panzoom/issues/577
- Option 2: Add something like a
maxPanRatio
- https://github.com/timmywil/panzoom/issues/476
- Option 1: Add another option to
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 withresizeObserver
? - Watch the CSS styles for the
content
DOM Element and adjust the calculations depending on if the element isblock
orinline
? Something similar mentioned here: https://github.com/timmywil/panzoom/issues/611#issuecomment-1050061902
- [ ] Add
'auto'
option tostartX, startY, startScale
to let Panzoom automatically calculate the appropriate x/y/scale which fits the content within the container when the instance is initialized andreset()
- [ ] 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 optionzoomPadding
. - [ ] Add another option to
contain
or something likemaxPanRatio
to help keep the user from moving way outside of the container and getting lost. Maybecontain: 'content'
, if we have thiscontent
concept? (Relates to #598, #504, #577, #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 orzoomToPoint()
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 usingzoomToPoint()
:-
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 thezoomToPoint
method to make things easier. (via https://github.com/timmywil/panzoom/issues/564#issuecomment-832791748)
-
- Suggestion to use
zoomToPoint
: https://github.com/timmywil/panzoom/issues/521#issuecomment-726823720-
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 theclientX
andclientY
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 https://github.com/timmywil/panzoom/issues/611)
- i.e. library consumer has to correct the
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.
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!