MobilePinchZoomControls
import { useEffect, useState, useRef } from "react";
import styled from "styled-components";
const Wrapper = styled.div`
width: 100%;
height: 100%;
overflow: auto;
`;
// NOTE: don't use .attrs((props) => , it makes scrollHeight won't update immediately on IOS device.
const ZoomTarget = styled.div`
transform-origin: 0% 0%;
transform: scale(${(props) => props.scale});
`;
const attachPinchZoomEvents = (element, setScale) => {
const { onMove, onEnd, onStart } = (() => {
let baseScale = 1;
let currentScale = 1;
let baseScrollLeft = 0;
let baseScrollTop = 0;
let scrollLeftOffsetUnit = 0;
let scrollTopOffsetUnit = 0;
const onMove = (scale) => {
currentScale = Math.max(baseScale * scale, 1.0);
setScale(currentScale);
element.scrollTo(
baseScrollLeft + (currentScale - baseScale) * scrollLeftOffsetUnit,
baseScrollTop + (currentScale - baseScale) * scrollTopOffsetUnit,
);
};
const onEnd = () => {
baseScale = currentScale;
};
const onStart = (anchorX, anchorY) => {
baseScrollLeft = element.scrollLeft;
baseScrollTop = element.scrollTop;
scrollLeftOffsetUnit =
((anchorX / 100) * element.scrollWidth) / baseScale;
scrollTopOffsetUnit =
((anchorY / 100) * element.scrollHeight) / baseScale;
};
return { onMove, onEnd, onStart };
})(setScale);
let start = {};
const distance = (event) => {
return Math.hypot(
event.touches[0].pageX - event.touches[1].pageX,
event.touches[0].pageY - event.touches[1].pageY,
);
};
const calcPercentage = (clientX, clientY) => {
const width = element.scrollWidth;
const height = element.scrollHeight;
const clickX =
clientX - element.getBoundingClientRect().left + element.scrollLeft;
const clickY =
clientY - element.getBoundingClientRect().top + element.scrollTop;
const percentageX = (clickX / width) * 100;
const percentageY = (clickY / height) * 100;
return { x: percentageX, y: percentageY };
};
const touchStart = (event) => {
if (event.touches.length === 2) {
const { x, y } = calcPercentage(
(event.touches[0].clientX + event.touches[1].clientX) / 2,
(event.touches[0].clientY + event.touches[1].clientY) / 2,
);
onStart(x, y);
event.preventDefault();
start.distance = distance(event);
}
};
const touchMove = (event) => {
if (event.touches.length === 2) {
event.preventDefault();
const deltaDistance = distance(event);
const scale = deltaDistance / start.distance;
const truncateScale = scale;
onMove(truncateScale);
}
};
element.addEventListener("touchstart", touchStart);
element.addEventListener("touchmove", touchMove);
element.addEventListener("touchend", onEnd);
return () => {
element.removeEventListener("touchstart", touchStart);
element.removeEventListener("touchmove", touchMove);
element.removeEventListener("touchend", onEnd);
};
};
const PinchZoomControls = ({ children }) => {
const wrapperRef = useRef(null);
const [scale, setScale] = useState(1);
useEffect(() => {
const clear = attachPinchZoomEvents(wrapperRef.current, setScale);
return clear;
}, []);
return (
<Wrapper ref={wrapperRef}>
<ZoomTarget scale={scale}>{children}</ZoomTarget>
</Wrapper>
);
};
export default PinchZoomControls;
Issue
Children is missing when scale gets bigger.
Resolved: Add css
transform: translate3d(0px, 0px, 0px)
to children, reference.