Home > Blockchain >  Zooming and panning a div with img tags using d3
Zooming and panning a div with img tags using d3

Time:08-17

I am trying to use d3 to zoom/pan a div tag containing img tags. I have the general idea but I'm not clear on the mathematics or library functions for recalculating coordinates. My code is incomplete around the calculations for zooming/panning, what are the necessary calculations here so that a user can zoom in/out and pan around the parent div?

        if (isZooming) {
          // rescale
          scale = k
          // translate x and y to acount for new scale
          tX = (x) * scale;
          tY = (y) * scale;
        } else { // panning
          scale = 1;
          // translate x and y from translate start?
          tX = x
          tY = y
        }

const images = [
"//cdn.pixabay.com/photo/2015/10/01/17/17/car-967387__480.png",
"//cdn.pixabay.com/photo/2016/02/23/17/29/banana-1218133__340.png"
];
// prevent from triggering re-render
const zoomHandler = d3.zoom();
function ZoomableDocs() {
  const zoomRef = React.useRef(null);
  React.useEffect(() => {
    d3.select(zoomRef.current).call(zoomHandler);
  }, []);
  const [startTransform, setStartTransform] = React.useState(null);
  const zoomStarted = React.useCallback((e) => {
    setStartTransform(e.transform);
  }, [setStartTransform]);
  const zoomed = React.useCallback((e) => {
    const { transform } = e;
    const { x, y, k } = transform;
    const isZooming = e.sourceEvent.type === 'mousewheel';
    const isPanning = e.sourceEvent.type === 'wheel';
    if (!isZooming && !isPanning) {
      return;
    }
    let tX, tY, scale;
    if (isZooming) {
      // rescale
      scale = k
      // translate x and y to acount for new scale
      tX = (x) * scale;
      tY = (y) * scale;
    } else { // panning
      scale = 1;
      // translate x and y from translate start?
      tX = x
      tY = y
    }
    d3.select(zoomRef.current).attr('style', `transform: translate3d(${tX}px,${tY},0) scale(${scale})`);
  }, [startTransform]);
  const zoomEnded = React.useCallback((e) => {
    setStartTransform(null);
  }, [setStartTransform]);
  React.useEffect(() => {
    zoomHandler
    .on('start', zoomStarted)
    .on('zoom', zoomed)
    .on('end', zoomEnded);
  }, [zoomStarted, zoomed, zoomEnded]);
  
  return (
    <div ref={zoomRef}>
      {images.map(imgUrl => <img key={imgUrl} src={imgUrl} />)}
    </div>
  );
}

ReactDOM.render(<ZoomableDocs/>, document.querySelector("#app"));
<script crossorigin src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js" integrity="sha512-MefNfAGJ/pEy89xLOFs3V6pYPs6AmUhXJrRlydI/9wZuGrqxmrdQ80zKHUcyadAcpH67teDZcBeS6oMJLPtTqw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="app" />

I have an application

CodePudding user response:

Applying transform to <div> is tricky. See a solution with positioning in the fiddle:

const ZoomableImage = ({src}) => {
  const divRef = React.useRef();
    
  React.useEffect(() => {
    if (!divRef.current) return;
    const outer = d3.select(divRef.current);
    const update = t => {
      outer.select('.inner')
        .style('transform', `scale(${t.k})`)
        .style('left', `${t.x}px`)
        .style('top', `${t.y}px`);
    };
  
    const onZoom = () => update(d3.event.transform);
    
    const divZoom = d3.zoom()
      .scaleExtent([0.1, 5])
      .on('zoom', onZoom);
    outer.call(divZoom);
  }, [divRef]);
    
  return (
    <div ref={divRef} className='outer'>
      <div className='inner'>
        <img src={src} /> 
      </div>
    </div>
  );
}

ReactDOM.render(<ZoomableImage src="//cdn.pixabay.com/photo/2015/10/01/17/17/car-967387__480.png"/>, document.querySelector("#container"));
.outer {
  position: relative;
}

.inner {
  position: absolute;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id='container'>
</div>

  • Related