UNPKG

react-zoom-image-hover

Version:
142 lines 5.9 kB
"use strict"; 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