Search code examples
svgzoominginlinesvgpanzoom

SVGPanZoom discards original viewBox


I am using SVGPanZoom to manage the zooming of an SVG image in my hybrid Android (for all intents and purposes the same behavior as in Chrome) app. While zooming works well I have found a strange issue. My original inline SVG element goes like this

 <svg id='puzzle' viewBox='0 0 1600 770' preserveAspectRatio='none'
  width='100vw' height='85.5vh' fill-rule='evenodd' clip-rule='evenodd' 
  stroke-linejoin='round' stroke-miterlimit='1.414' 
  xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http:// 
  www.w3.org/1999/xlink'>

Initially this SVG element is empty and gets populated programmatically from JavaScript at run time after which I initiate SVGPanZoom as follows

var panZoom = svgPanZoom('#puzzle', 
{panEnabled:false,controlIconsEnabled:false,
zoomEnabled:true,dblClickZoomEnabled:true,onZoom:postZoom});

panZoom.refreshRate = 10;
panZoom.zoomScaleSensitivity = 0.02;

The problem I have run into is this - I want my SVG image to fill the available area, 100vw x 85.5vhcompletely to do which I instruct it via the preserveAspectRatio="none"attribute above along with the viewBox="0 0 1600 770" attribute. I have found that this works - so long as I don't use SVGPanZoom. As soon as I initiate panZoom thezoomBox`attribute gets stripped out and I end up with an image that does not quite behave in terms of its default stretching/filling behavior.

SVGPanZoom is widely used so I assume that this behavior is down to me not quite setting it up properly. Dipping into the code I have found SVGPanZoom creates a cacheViewBoxand then proceeds to remove the original zoomBox attribute.

Which is fine if after that zooming works and the original behavior of the application does not change which is not what I find. What am I doing wrong here?


Solution

  • A note for anyone running into this thread. In the end I abandoned SVGPanZoom and decided to eschew the route of using any pan/zoom library at all. At the same time I decided to completely stop using the SVG viewBox and handle all zooming/panning entirely on my own through SVG transforms. The core steps involved

    • Wrap the entire SVG contents in a group to make it easier to manage the transform. I use the id attribute gOuter for this group
    • Set an initial scale for the SVG to occupy the desired client rectangle. In my case I had an original viewBox of 0 0 1600 770 intended to occupy 100% of screen width and 85% of screen height. So my scaling was scaleX = 1600/window.innerWidth and scaleY = 770/)0.85*window.innerHeight).
    • Apply this initial transform to the wrapping outer group, gOuter.setAttribute('transform','0 0 scaleX,scaleY)
    • Now in order to zoom to a an object whose virtual top left hand coordinates in the original viewBox were Ox,Oy you would use the transform

    gOuter.setAttribute('transform',
    scale(scaleX,scaleY) translate(-Ox,-Oy) scale(2*scaleX,2*scaleY) translate(Ox,Oy))

    to zoom in by a factor of x 2. The important things to understand here

    • In SVG transformations are applied right to left.
    • Here we are translating the zoom point to the top l.h.s. scaling and then translating it back to its original location.
    • The problem is that we also need to allow for the original level of zoom through the initial scaling so we tag that on as one last transform

    This leaves you in complete control of the zooming process and as a fringe benefit the operation becomes considerably more smooth than when using a pan/zoom library.