react-zoom-image-hover
Version:
This package is for zooming image on hover
142 lines • 5.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
/**
* Zoom component that provides image zoom on hover with smooth transitions
* and customizable zoom behavior.
*
* @example
* ```tsx
* <Zoom
* src="/path/to/image.jpg"
* width={400}
* height={300}
* zoomScale={2}
* alt="Product image"
* />
* ```
*/
const Zoom = ({ className, innerClassName, zoomScale = 1.5, height, width, style, src, transitionTime = 0.1, alt, objectFit = 'contain', objectPosition = 'center', enableTouch = true, throttleDelay = 16, transitionTimingFunction = 'ease-out', }) => {
const containerRef = (0, react_1.useRef)(null);
const throttleTimerRef = (0, react_1.useRef)(null);
const lastPositionRef = (0, react_1.useRef)(null);
const [isZoomed, setIsZoomed] = (0, react_1.useState)(false);
const [mouseX, setMouseX] = (0, react_1.useState)(50);
const [mouseY, setMouseY] = (0, react_1.useState)(50);
/**
* Throttled function to update mouse position
* Improves performance by limiting update frequency
* Uses a more efficient throttle that ensures the last position is always applied
*/
const updateMousePosition = (0, react_1.useCallback)((x, y) => {
// Store the latest position
lastPositionRef.current = { x, y };
// If no timer is running, set one up
if (throttleTimerRef.current === null) {
throttleTimerRef.current = window.setTimeout(() => {
// Always apply the most recent position
if (lastPositionRef.current) {
setMouseX(lastPositionRef.current.x);
setMouseY(lastPositionRef.current.y);
}
throttleTimerRef.current = null;
lastPositionRef.current = null;
}, throttleDelay);
}
}, [throttleDelay]);
/**
* Calculate mouse position relative to container
*/
const calculatePosition = (0, react_1.useCallback)((clientX, clientY) => {
if (!containerRef.current)
return;
const rect = containerRef.current.getBoundingClientRect();
// clientX/clientY are viewport-relative, and getBoundingClientRect() also returns
// viewport-relative coordinates, so we don't need to account for scroll
const x = ((clientX - rect.left) / rect.width) * 100;
const y = ((clientY - rect.top) / rect.height) * 100;
// Clamp values to ensure they stay within bounds
const clampedX = Math.max(0, Math.min(100, x));
const clampedY = Math.max(0, Math.min(100, y));
updateMousePosition(clampedX, clampedY);
}, [updateMousePosition]);
/**
* Handle mouse enter event
*/
const handleMouseEnter = (0, react_1.useCallback)(() => {
setIsZoomed(true);
}, []);
/**
* Handle mouse leave event
*/
const handleMouseLeave = (0, react_1.useCallback)(() => {
setIsZoomed(false);
// Clear throttle timer on mouse leave and apply any pending position
if (throttleTimerRef.current !== null) {
clearTimeout(throttleTimerRef.current);
throttleTimerRef.current = null;
}
if (lastPositionRef.current) {
setMouseX(lastPositionRef.current.x);
setMouseY(lastPositionRef.current.y);
lastPositionRef.current = null;
}
}, []);
/**
* Handle mouse movement
*/
const handleMouseMove = (0, react_1.useCallback)((e) => {
calculatePosition(e.clientX, e.clientY);
}, [calculatePosition]);
/**
* Handle touch movement for mobile devices
*/
const handleTouchMove = (0, react_1.useCallback)((e) => {
if (!enableTouch || e.touches.length === 0)
return;
e.preventDefault();
const touch = e.touches[0];
calculatePosition(touch.clientX, touch.clientY);
}, [enableTouch, calculatePosition]);
/**
* Handle touch start
*/
const handleTouchStart = (0, react_1.useCallback)((e) => {
if (!enableTouch || e.touches.length === 0)
return;
setIsZoomed(true);
const touch = e.touches[0];
calculatePosition(touch.clientX, touch.clientY);
}, [enableTouch, calculatePosition]);
/**
* Handle touch end
*/
const handleTouchEnd = (0, react_1.useCallback)(() => {
if (enableTouch) {
setIsZoomed(false);
}
}, [enableTouch]);
/**
* Memoized container styles
*/
const containerStyle = (0, react_1.useMemo)(() => (Object.assign(Object.assign({}, style), { height,
width, overflow: 'hidden', position: 'relative', cursor: 'zoom-in' })), [style, height, width]);
/**
* Memoized image styles with zoom effect
*/
const imageStyle = (0, react_1.useMemo)(() => ({
width: '100%',
height: '100%',
objectFit,
objectPosition,
transition: `transform ${transitionTime}s ${transitionTimingFunction}`,
transformOrigin: `${mouseX}% ${mouseY}%`,
transform: isZoomed ? `scale(${zoomScale})` : 'scale(1)',
willChange: 'transform',
display: 'block',
}), [objectFit, objectPosition, transitionTime, transitionTimingFunction, mouseX, mouseY, isZoomed, zoomScale]);
return ((0, jsx_runtime_1.jsx)("div", { ref: containerRef, className: className, style: containerStyle, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: (0, jsx_runtime_1.jsx)("img", { src: src, alt: alt || '', className: innerClassName, style: imageStyle, draggable: false }) }));
};
exports.default = Zoom;
//# sourceMappingURL=Zoom.js.map