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>