react-zoom-pan-pinch
react-zoom-pan-pinch copied to clipboard
Zoom on center using setTransform
Hi! I know this is not an issue but a discussion, but there is not a discussion tab in this repository. In the feature I want to implement, I need to have a controlled zoom (10%, 20%... 100%, 110%... 400%) so I am not using zoomIn and zoomOut handlers but setTransform. I am not able to implement the algorithm to calculate x and y position. Any of you have been able to do it? This is a codesandbox I forked and change in order to do it. I would expect to zoom in the center of the transform component, and not always in the same x,y position.
https://codesandbox.io/s/zoom-pan-pinch-zoom-center-b4dfn?file=/src/App.tsx
I have tried to replicate some logic I found in the zoomIn, zoomOut original code but I didn' achieve it.
export function handleZoomToViewCenter(
contextInstance: ReactZoomPanPinchContext,
delta: number,
step: number,
animationTime: number,
animationType: keyof typeof animations,
): void {
const { wrapperComponent } = contextInstance;
const { scale, positionX, positionY } = contextInstance.transformState;
if (!wrapperComponent) return console.error("No WrapperComponent found");
const wrapperWidth = wrapperComponent.offsetWidth;
const wrapperHeight = wrapperComponent.offsetHeight;
const mouseX = (wrapperWidth / 2 - positionX) / scale;
const mouseY = (wrapperHeight / 2 - positionY) / scale;
const newScale = handleCalculateButtonZoom(contextInstance, delta, step);
const targetState = handleZoomToPoint(
contextInstance,
newScale,
mouseX,
mouseY,
);
if (!targetState) {
return console.error(
"Error during zoom event. New transformation state was not calculated.",
);
}
animate(contextInstance, targetState, animationTime, animationType);
}
Thanks!
@ignlopezsanchez Im running into the same issue. Did you manage to find a way to zoom to the center of the part of the image that is in viewport?
@ignlopezsanchez I'm running into a similar issue where I want to zoom in to the mouse position...
@sanderkooger @ecemac hi! I stopped trying as it was not urgent for my project. But definitely this enhancement would add a lot of value to the library.
@sanderkooger @ignlopezsanchez I solved my problem, maybe it can also help you: react-zoom-pan-pinch zoom in with single click
@ecemac and @ignlopezsanchez
This is our current implementation. it responds to pinch and it has a slider that works too to show and set zoomlevel.
const Transition = React.forwardRef(
(
props: TransitionProps & {
children: React.ReactElement;
},
ref: React.Ref<unknown>
) => {
return <Slide direction="up" ref={ref} {...props} />;
}
);
export default function ImageZoom(props: ImageZoomProps): JSX.Element {
const { open, setOpen, activeImage, loading } = props;
const styles = Styles();
const isPortrait = activeImage?.height > activeImage?.width;
const imgStyles: React.CSSProperties = {
height: isPortrait ? '100vh' : 'auto',
width: isPortrait ? 'auto' : '100vw'
};
const zoomAnimationTime = 150;
const minScale = 1;
const maxScale = 8;
const [zoomInstance, setZoomInstance] = useState<ReactZoomPanPinchRef>(null);
const [currentZoomScale, setCurrentZoomScale] = useState<number>(1);
const handleZoomSlider2 = (event: Event, newValue: number) => {
// Check below link for Centerd zoom with slider
// https://github.com/prc5/react-zoom-pan-pinch/issues/137
const currentZoom = zoomInstance.state.scale;
const factor = Math.log(newValue / currentZoom);
if (newValue > currentZoom) {
// Can not have animation time it breaks teh responsiveness
// console.log('I need to zoom in');
zoomInstance.zoomIn(factor, 0);
} else {
// console.log('zoom Out');
zoomInstance.zoomOut(-factor, 0);
}
};
const handleOnClickZoomIn = () => {
zoomInstance.zoomIn(0.25, zoomAnimationTime);
};
const handleOnClickZoomOut = () => {
zoomInstance.zoomOut(0.25, zoomAnimationTime);
};
useEffect(() => {
setCurrentZoomScale(zoomInstance?.state?.scale);
}, [zoomInstance?.state?.scale]);
if (loading) {
return null;
}
return (
<Dialog
sx={styles.dialog}
data-testid="ImageZoomDialog"
open={open}
fullScreen
TransitionComponent={Transition}
>
{/* <Typography sx={styles.testText}>
currentZoomScale: {currentZoomScale}
<br />
<br />
</Typography> */}
<IconButton
data-testid="TitleBarCloseButton"
sx={styles.closeButton}
disableRipple
edge="start"
color="inherit"
aria-label="Close"
onClick={() => setOpen(false)}
>
<CloseTwoTone />
</IconButton>
<Box sx={styles.mainDiv} display="flex" justifyContent="center" alignItems="center">
<TransformWrapper
initialScale={currentZoomScale}
centerZoomedOut
centerOnInit
limitToBounds
minScale={minScale}
maxScale={maxScale}
ref={(ref) => setZoomInstance(ref)}
onZoom={(zoom) => setCurrentZoomScale(zoom.state.scale)}
onZoomStop={(zoom) => setCurrentZoomScale(zoom.state.scale)}
onInit={(zoomzoom) => setCurrentZoomScale(zoomzoom.state.scale)}
// zoomAnimation={{ disabled: true }}
>
<TransformComponent>
<Image
alt="Zoom Image"
priority
placeholder="blur"
style={imgStyles}
src={activeImage?.imgUrl}
blurDataURL={activeImage?.blurUrl}
quality={100}
height={activeImage?.height}
width={activeImage?.width}
/>
</TransformComponent>
</TransformWrapper>
</Box>
<Grid sx={styles.sliderContainer} container justifyContent="center" justifyItems="center">
<Grid item xs={12} md={4}>
<Stack
paddingX={3}
paddingBottom={3}
spacing={2}
direction="row"
sx={{ mb: 1 }}
alignItems="center"
>
<IconButton onClick={() => handleOnClickZoomOut()}>
<ZoomOutTwoTone />
</IconButton>
{/* <Box sx={{ width: '100%' }} /> */}
<Slider
sx={{}}
step={0.01}
aria-label="Zoom Slider"
value={currentZoomScale || 1}
max={maxScale}
defaultValue={minScale}
min={minScale}
onChange={handleZoomSlider2}
// color="secondary"
/>
<IconButton onClick={() => handleOnClickZoomIn()}>
<ZoomInTwoTone />
</IconButton>
</Stack>
</Grid>
</Grid>
</Dialog>
);
}
I don't have the time to explain everything, but you can see the zoom handlers and math in there. You can also see in what props we pass the information.
Hope it helps `
Note: The scale calculation was changed at #367.
I'm thinking of two ways to enhance this package:
- If
newPositionX
/Y
passed tosetTransform
is NaN, zoom to the center instead of the top left. (BREAKING CHANGE) - Add a new function named
zoomToViewCenter(scale, animationTime, animationType)
for example.
Do you have any other ideas?
Code for zoom In:
newScale = scale + 0.1;
var element = document.getElementsByClassName('react-transform-component');
var style = window.getComputedStyle(element[0]);
var matrix = new WebKitCSSMatrix(style.transform);
var ratio = (newScale - scale) / scale + 1;
var x = (matrix.m41 - window.innerWidth / 2 * (1 - scale / newScale)) * ratio;
var y = (matrix.m42 - window.innerHeight / 2 * (1 - scale / newScale)) * ratio
setTransform(x, y, newScale, 0);
Code for zoom out:
newScale = scale - 0.1;
var element = document.getElementsByClassName('react-transform-component');
var style = window.getComputedStyle(element[0]);
var matrix = new WebKitCSSMatrix(style.transform);
var ratio = (newScale - scale) / scale + 1;
var x = (matrix.m41 - window.innerWidth / 2 * (1 - scale / newScale)) * ratio;
var y = (matrix.m42 - window.innerHeight / 2 * (1 - scale / newScale)) * ratio
setTransform(x, y, newScale, 0);
The matrix.m41 and matrix.m42 is used to retrieve the x and y value for the translation inside the element. Consider you zoom in from 100% to 110%, the width of an element will be incrased from 1000px to 1100px, the ratio of percentage change can be found from (newScale - scale) / scale + 1.
The formula can be break into two parts
var x = matrix.m41 * ratio + window.innerWidth / 2 * ratio * (1 - scale / newScale)
If you take the top left point as origin and zoom in an element, the new x value will be
matrix.m41 * ratio
When you zoom into the picture, edges of the picture will be hidden, it comes with the second part of formula
window.innerWidth / 2 * ratio * (1 - scale / newScale)
Consider both the screen and element is 1000px, when you zoom in from 100% to 110%, the screen can only show part of the element that is (scale / newScale) * 1000px = 909px. Loss of 91px will be hidden on x-axis that can be calcuated from (1 - scale / newScale) * 1000px
Using the same idea, when you zoom on the center, part of the x-axis and y-axis will be hidden in the process. The new x and y value will be moving right and down a little bit by adding the second part of forumla.