next-360-image-viewer
Version:
Display a set of images in a draggable 360 degree viewer.
177 lines (149 loc) • 5.27 kB
text/typescript
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,
};
};