Adds touch support to lightbox
This commit is contained in:
parent
48229332ea
commit
54e7cd21fc
1 changed files with 88 additions and 40 deletions
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue