Adds touch support to lightbox

This commit is contained in:
Josh Perez 2021-10-04 16:12:14 -04:00 committed by GitHub
parent 48229332ea
commit 54e7cd21fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -73,7 +73,14 @@ export function Lightbox({
const imageRef = useRef<HTMLImageElement | null>(null); const imageRef = useRef<HTMLImageElement | null>(null);
const [imagePanStyle, setImagePanStyle] = useState<CSSProperties>({}); const [imagePanStyle, setImagePanStyle] = useState<CSSProperties>({});
const zoomCoordsRef = useRef< const zoomCoordsRef = useRef<
| { screenWidth: number; screenHeight: number; x: number; y: number } | {
initX: number;
initY: number;
screenWidth: number;
screenHeight: number;
x: number;
y: number;
}
| undefined | undefined
>(); >();
@ -231,52 +238,66 @@ export function Lightbox({
}; };
}, [isViewOnce, isAttachmentGIF, onTimeUpdate, playVideo, videoElement]); }, [isViewOnce, isAttachmentGIF, onTimeUpdate, playVideo, videoElement]);
const positionImage = useCallback((ev?: MouseEvent) => { const positionImage = useCallback(
const imageNode = imageRef.current; (ev?: { clientX: number; clientY: number }) => {
const zoomCoords = zoomCoordsRef.current; const imageNode = imageRef.current;
if (!imageNode || !zoomCoords) { const zoomCoords = zoomCoordsRef.current;
return; if (!imageNode || !zoomCoords) {
} return;
}
if (ev) { if (ev) {
zoomCoords.x = ev.clientX; zoomCoords.x = ev.clientX;
zoomCoords.y = ev.clientY; zoomCoords.y = ev.clientY;
} }
const shouldTransformX = imageNode.naturalWidth > zoomCoords.screenWidth; const shouldTransformX = imageNode.naturalWidth > zoomCoords.screenWidth;
const shouldTransformY = imageNode.naturalHeight > zoomCoords.screenHeight; const shouldTransformY =
imageNode.naturalHeight > zoomCoords.screenHeight;
const nextImagePanStyle: CSSProperties = { const nextImagePanStyle: CSSProperties = {
left: '50%', left: '50%',
top: '50%', top: '50%',
}; };
let translateX = '-50%'; let translateX = '-50%';
let translateY = '-50%'; let translateY = '-50%';
if (shouldTransformX) { if (shouldTransformX) {
const scaleX = const offset = imageNode.offsetWidth - zoomCoords.screenWidth;
(-1 / zoomCoords.screenWidth) *
(imageNode.offsetWidth - zoomCoords.screenWidth);
translateX = `${zoomCoords.x * scaleX}px`; const scaleX = (-1 / zoomCoords.screenWidth) * offset;
nextImagePanStyle.left = 0;
}
if (shouldTransformY) { const posX = Math.max(
const scaleY = 0,
(-1 / zoomCoords.screenHeight) * Math.min(zoomCoords.screenWidth, zoomCoords.x)
(imageNode.offsetHeight - zoomCoords.screenHeight); );
translateY = `${zoomCoords.y * scaleY}px`; translateX = `${posX * scaleX}px`;
nextImagePanStyle.top = 0; nextImagePanStyle.left = 0;
} }
setImagePanStyle({ if (shouldTransformY) {
...nextImagePanStyle, const offset = imageNode.offsetHeight - zoomCoords.screenHeight;
transform: `translate(${translateX}, ${translateY})`,
}); const scaleY = (-1 / zoomCoords.screenHeight) * offset;
}, []);
const posY = Math.max(
0,
Math.min(zoomCoords.screenHeight, zoomCoords.y)
);
translateY = `${posY * scaleY}px`;
nextImagePanStyle.top = 0;
}
setImagePanStyle({
...nextImagePanStyle,
transform: `translate(${translateX}, ${translateY})`,
});
},
[]
);
function canPanImage(): boolean { function canPanImage(): boolean {
const imageNode = imageRef.current; const imageNode = imageRef.current;
@ -288,6 +309,29 @@ export function Lightbox({
); );
} }
const handleTouchMove = useCallback(
(ev: TouchEvent) => {
const imageNode = imageRef.current;
const zoomCoords = zoomCoordsRef.current;
ev.preventDefault();
ev.stopPropagation();
if (!imageNode || !zoomCoords) {
return;
}
const [touch] = ev.touches;
const { initX, initY } = zoomCoords;
positionImage({
clientX: initX + (initX - touch.clientX),
clientY: initY + (initY - touch.clientY),
});
},
[positionImage]
);
useEffect(() => { useEffect(() => {
const imageNode = imageRef.current; const imageNode = imageRef.current;
let hasListener = false; let hasListener = false;
@ -295,14 +339,16 @@ export function Lightbox({
if (imageNode && zoomType !== ZoomType.None && canPanImage()) { if (imageNode && zoomType !== ZoomType.None && canPanImage()) {
hasListener = true; hasListener = true;
document.addEventListener('mousemove', positionImage); document.addEventListener('mousemove', positionImage);
document.addEventListener('touchmove', handleTouchMove);
} }
return () => { return () => {
if (hasListener) { if (hasListener) {
document.removeEventListener('mousemove', positionImage); document.removeEventListener('mousemove', positionImage);
document.removeEventListener('touchmove', handleTouchMove);
} }
}; };
}, [positionImage, zoomType]); }, [handleTouchMove, positionImage, zoomType]);
const caption = attachment?.caption; const caption = attachment?.caption;
@ -346,8 +392,10 @@ export function Lightbox({
if (canPanImage()) { if (canPanImage()) {
setZoomType(ZoomType.ZoomAndPan); setZoomType(ZoomType.ZoomAndPan);
zoomCoordsRef.current = { zoomCoordsRef.current = {
screenWidth: document.documentElement.clientWidth, initX: event.clientX,
initY: event.clientY,
screenHeight: document.documentElement.clientHeight, screenHeight: document.documentElement.clientHeight,
screenWidth: document.documentElement.clientWidth,
x: event.clientX, x: event.clientX,
y: event.clientY, y: event.clientY,
}; };