mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-21 03:51:45 +08:00
refactor: image preview plugin (#3457)
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import type { MouseEvent as ReactMouseEvent, RefObject } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
interface UseZoomControlsProps {
|
||||
zoomRef: RefObject<HTMLDivElement>;
|
||||
imageRef: RefObject<HTMLImageElement>;
|
||||
}
|
||||
|
||||
export const useZoomControls = ({
|
||||
zoomRef,
|
||||
imageRef,
|
||||
}: UseZoomControlsProps) => {
|
||||
const [currentScale, setCurrentScale] = useState<number>(1);
|
||||
const [isZoomedBigger, setIsZoomedBigger] = useState<boolean>(false);
|
||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||
const [mouseX, setMouseX] = useState<number>(0);
|
||||
const [mouseY, setMouseY] = useState<number>(0);
|
||||
const [dragBeforeX, setDragBeforeX] = useState<number>(0);
|
||||
const [dragBeforeY, setDragBeforeY] = useState<number>(0);
|
||||
const [imagePos, setImagePos] = useState<{ x: number; y: number }>({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(event: ReactMouseEvent) => {
|
||||
event?.preventDefault();
|
||||
setIsDragging(true);
|
||||
const image = imageRef.current;
|
||||
if (image && isZoomedBigger) {
|
||||
image.style.cursor = 'grab';
|
||||
const rect = image.getBoundingClientRect();
|
||||
setDragBeforeX(rect.left);
|
||||
setDragBeforeY(rect.top);
|
||||
setMouseX(event.clientX);
|
||||
setMouseY(event.clientY);
|
||||
}
|
||||
},
|
||||
[imageRef, isZoomedBigger]
|
||||
);
|
||||
|
||||
const handleDrag = useCallback(
|
||||
(event: ReactMouseEvent) => {
|
||||
event?.preventDefault();
|
||||
const image = imageRef.current;
|
||||
|
||||
if (isDragging && image && isZoomedBigger) {
|
||||
image.style.cursor = 'grabbing';
|
||||
const currentX = imagePos.x;
|
||||
const currentY = imagePos.y;
|
||||
const newPosX = currentX + event.clientX - mouseX;
|
||||
const newPosY = currentY + event.clientY - mouseY;
|
||||
image.style.transform = `translate(${newPosX}px, ${newPosY}px)`;
|
||||
}
|
||||
},
|
||||
[
|
||||
imagePos.x,
|
||||
imagePos.y,
|
||||
imageRef,
|
||||
isDragging,
|
||||
isZoomedBigger,
|
||||
mouseX,
|
||||
mouseY,
|
||||
]
|
||||
);
|
||||
|
||||
const dragEndImpl = useCallback(() => {
|
||||
setIsDragging(false);
|
||||
|
||||
const image = imageRef.current;
|
||||
if (image && isZoomedBigger && isDragging) {
|
||||
image.style.cursor = 'pointer';
|
||||
const rect = image.getBoundingClientRect();
|
||||
const newPos = { x: rect.left, y: rect.top };
|
||||
const currentX = imagePos.x;
|
||||
const currentY = imagePos.y;
|
||||
const newPosX = currentX + newPos.x - dragBeforeX;
|
||||
const newPosY = currentY + newPos.y - dragBeforeY;
|
||||
setImagePos({ x: newPosX, y: newPosY });
|
||||
}
|
||||
}, [
|
||||
dragBeforeX,
|
||||
dragBeforeY,
|
||||
imagePos.x,
|
||||
imagePos.y,
|
||||
imageRef,
|
||||
isDragging,
|
||||
isZoomedBigger,
|
||||
]);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: ReactMouseEvent) => {
|
||||
event.preventDefault();
|
||||
dragEndImpl();
|
||||
},
|
||||
[dragEndImpl]
|
||||
);
|
||||
|
||||
const handleMouseUp = useCallback(() => {
|
||||
if (isDragging) {
|
||||
dragEndImpl();
|
||||
}
|
||||
}, [isDragging, dragEndImpl]);
|
||||
|
||||
const checkZoomSize = useCallback(() => {
|
||||
const { current: zoomArea } = zoomRef;
|
||||
if (zoomArea) {
|
||||
const image = zoomArea.querySelector('img');
|
||||
if (image) {
|
||||
const zoomedWidth = image.naturalWidth * currentScale;
|
||||
const zoomedHeight = image.naturalHeight * currentScale;
|
||||
const containerWidth = window.innerWidth;
|
||||
const containerHeight = window.innerHeight;
|
||||
setIsZoomedBigger(
|
||||
zoomedWidth > containerWidth || zoomedHeight > containerHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [currentScale, zoomRef]);
|
||||
|
||||
const zoomIn = useCallback(() => {
|
||||
const image = imageRef.current;
|
||||
|
||||
if (image && currentScale < 2) {
|
||||
const newScale = currentScale + 0.1;
|
||||
setCurrentScale(newScale);
|
||||
image.style.width = `${image.naturalWidth * newScale}px`;
|
||||
image.style.height = `${image.naturalHeight * newScale}px`;
|
||||
}
|
||||
}, [imageRef, currentScale]);
|
||||
|
||||
const zoomOut = useCallback(() => {
|
||||
const image = imageRef.current;
|
||||
if (image && currentScale > 0.2) {
|
||||
const newScale = currentScale - 0.1;
|
||||
setCurrentScale(newScale);
|
||||
image.style.width = `${image.naturalWidth * newScale}px`;
|
||||
image.style.height = `${image.naturalHeight * newScale}px`;
|
||||
const zoomedWidth = image.naturalWidth * newScale;
|
||||
const zoomedHeight = image.naturalHeight * newScale;
|
||||
const containerWidth = window.innerWidth;
|
||||
const containerHeight = window.innerHeight;
|
||||
if (zoomedWidth > containerWidth || zoomedHeight > containerHeight) {
|
||||
image.style.transform = `translate(0px, 0px)`;
|
||||
setImagePos({ x: 0, y: 0 });
|
||||
}
|
||||
}
|
||||
}, [imageRef, currentScale]);
|
||||
|
||||
const resetZoom = useCallback(() => {
|
||||
const image = imageRef.current;
|
||||
if (image) {
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const margin = 0.2;
|
||||
|
||||
const availableWidth = viewportWidth * (1 - margin);
|
||||
const availableHeight = viewportHeight * (1 - margin);
|
||||
|
||||
const widthRatio = availableWidth / image.naturalWidth;
|
||||
const heightRatio = availableHeight / image.naturalHeight;
|
||||
|
||||
const newScale = Math.min(widthRatio, heightRatio);
|
||||
setCurrentScale(newScale);
|
||||
image.style.width = `${image.naturalWidth * newScale}px`;
|
||||
image.style.height = `${image.naturalHeight * newScale}px`;
|
||||
image.style.transform = 'translate(0px, 0px)';
|
||||
setImagePos({ x: 0, y: 0 });
|
||||
checkZoomSize();
|
||||
}
|
||||
}, [imageRef, checkZoomSize]);
|
||||
|
||||
const resetScale = useCallback(() => {
|
||||
const image = imageRef.current;
|
||||
if (image) {
|
||||
setCurrentScale(1);
|
||||
image.style.width = `${image.naturalWidth}px`;
|
||||
image.style.height = `${image.naturalHeight}px`;
|
||||
image.style.transform = 'translate(0px, 0px)';
|
||||
setImagePos({ x: 0, y: 0 });
|
||||
}
|
||||
}, [imageRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = (event: WheelEvent) => {
|
||||
const { deltaY } = event;
|
||||
if (deltaY > 0) {
|
||||
zoomOut();
|
||||
} else if (deltaY < 0) {
|
||||
zoomIn();
|
||||
}
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
checkZoomSize();
|
||||
};
|
||||
|
||||
checkZoomSize();
|
||||
|
||||
window.addEventListener('wheel', handleScroll, { passive: false });
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('wheel', handleScroll);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
window.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
}, [zoomIn, zoomOut, checkZoomSize, handleMouseUp]);
|
||||
|
||||
return {
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
resetZoom,
|
||||
resetScale,
|
||||
isZoomedBigger,
|
||||
currentScale,
|
||||
handleDragStart,
|
||||
handleDrag,
|
||||
handleDragEnd,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user