UNPKG

next-360-image-viewer

Version:

Display a set of images in a draggable 360 degree viewer.

177 lines (149 loc) 5.27 kB
import { useEffect, useRef, useState } from 'react'; import type { NEXT_IMAGE_TURNTABLE_PROPS } from './types'; interface USE_TURNTABLE_STATE_PROPS extends Required<Pick<NEXT_IMAGE_TURNTABLE_PROPS, 'initialimageindex' | 'movementsensitivity'>> { /** Number of images starting from zero. */ imagescount: number; autoplay?: boolean; autoplayspeed?: number; autoplaydirection?: 'forward' | 'backward'; autopauseonhover?: boolean; } export const USE_TURNTABLE_STATE = ({ initialimageindex, imagescount, movementsensitivity, autoplay = false, autoplayspeed = 2000, autoplaydirection = 'forward', autopauseonhover = true, }: USE_TURNTABLE_STATE_PROPS) => { const [activeimageindex, setactiveimageindex] = useState(initialimageindex); const [isautoplayactive, setisautoplayactive] = useState(autoplay); const [ispaused, setispaused] = useState(false); const ref = useRef<HTMLDivElement>(null); const autoplayintervalref = useRef<number | null>(null); useEffect(() => { setactiveimageindex(initialimageindex); }, [initialimageindex]); // Autoplay functions const START_AUTOPLAY = () => { setisautoplayactive(true); setispaused(false); }; const STOP_AUTOPLAY = () => { setisautoplayactive(false); setispaused(false); if (autoplayintervalref.current) { clearInterval(autoplayintervalref.current); autoplayintervalref.current = null; } }; const PAUSE_AUTOPLAY = () => { setispaused(true); }; const RESUME_AUTOPLAY = () => { setispaused(false); }; // Autoplay interval management useEffect(() => { if (isautoplayactive && !ispaused) { autoplayintervalref.current = setInterval(() => { setactiveimageindex((prev) => { if (autoplaydirection === 'forward') { return prev + 1 > imagescount ? 0 : prev + 1; } else { return prev - 1 < 0 ? imagescount : prev - 1; } }); }, autoplayspeed); } else { if (autoplayintervalref.current) { clearInterval(autoplayintervalref.current); autoplayintervalref.current = null; } } return () => { if (autoplayintervalref.current) { clearInterval(autoplayintervalref.current); autoplayintervalref.current = null; } }; }, [isautoplayactive, ispaused, autoplayspeed, autoplaydirection, imagescount]); useEffect(() => { const target = ref.current as HTMLDivElement; let prevdragposition = 0; const INCREMENT_ACTIVE_INDEX = () => { setactiveimageindex((prev) => (prev + 1 > imagescount ? 0 : prev + 1)); }; const DECREMENT_ACTIVE_INDEX = () => { setactiveimageindex((prev) => (prev - 1 < 0 ? imagescount : prev - 1)); }; const HANDLE_KEY_DOWN = (ev: KeyboardEvent) => { if (ev.key === 'ArrowLeft') { DECREMENT_ACTIVE_INDEX(); } else if (ev.key === 'ArrowRight') { INCREMENT_ACTIVE_INDEX(); } }; const HANDLE_POINTER_MOVE = (ev: PointerEvent) => { const distancedragged = prevdragposition - ev.clientX; if (distancedragged <= -movementsensitivity) { prevdragposition = prevdragposition + movementsensitivity; INCREMENT_ACTIVE_INDEX(); } if (distancedragged >= movementsensitivity) { prevdragposition = prevdragposition - movementsensitivity; DECREMENT_ACTIVE_INDEX(); } }; const HANDLE_POINTER_UP = () => { window.removeEventListener('pointermove', HANDLE_POINTER_MOVE); window.removeEventListener('pointerup', HANDLE_POINTER_UP); }; const HANDLE_POINTER_DOWN = (ev: PointerEvent) => { if (ev.button == 2) { return; } prevdragposition = ev.clientX; window.addEventListener('pointermove', HANDLE_POINTER_MOVE, { passive: true }); window.addEventListener('pointerup', HANDLE_POINTER_UP, { passive: true }); }; const HANDLE_MOUSE_ENTER = () => { if (autopauseonhover && isautoplayactive) { PAUSE_AUTOPLAY(); } }; const HANDLE_MOUSE_LEAVE = () => { if (autopauseonhover && isautoplayactive) { RESUME_AUTOPLAY(); } }; target.addEventListener('keydown', HANDLE_KEY_DOWN, { capture: true }); target.addEventListener('pointerdown', HANDLE_POINTER_DOWN, { capture: true }); if (autopauseonhover) { target.addEventListener('mouseenter', HANDLE_MOUSE_ENTER); target.addEventListener('mouseleave', HANDLE_MOUSE_LEAVE); } return () => { target.removeEventListener('keydown', HANDLE_KEY_DOWN, { capture: true }); target.removeEventListener('pointerdown', HANDLE_POINTER_DOWN, { capture: true }); window.removeEventListener('pointerup', HANDLE_POINTER_UP); window.removeEventListener('pointermove', HANDLE_POINTER_MOVE); if (autopauseonhover) { target.removeEventListener('mouseenter', HANDLE_MOUSE_ENTER); target.removeEventListener('mouseleave', HANDLE_MOUSE_LEAVE); } }; }, [imagescount, movementsensitivity, autopauseonhover, isautoplayactive]); return { ref, activeimageindex, isautoplayactive, ispaused, START_AUTOPLAY, STOP_AUTOPLAY, PAUSE_AUTOPLAY, RESUME_AUTOPLAY, }; };