@react-three/drei
Version:
useful add-ons for react-three-fiber
94 lines (85 loc) • 3.42 kB
JavaScript
import * as React from 'react';
import { useThree } from '@react-three/fiber';
function CycleRaycast({
onChanged,
portal,
preventDefault = true,
scroll = true,
keyCode = 9
}) {
const cycle = React.useRef(0);
const setEvents = useThree(state => state.setEvents);
const get = useThree(state => state.get);
const gl = useThree(state => state.gl);
React.useEffect(() => {
var _portal$current;
let hits = [];
let lastEvent = undefined;
const prev = get().events.filter;
const target = (_portal$current = portal == null ? void 0 : portal.current) !== null && _portal$current !== void 0 ? _portal$current : gl.domElement.parentNode;
// Render custom status
const renderStatus = () => target && onChanged && onChanged(hits, Math.round(cycle.current) % hits.length);
// Overwrite the raycasters custom filter (this only exists in r3f)
setEvents({
filter: (intersections, state) => {
// Reset cycle when the intersections change
let clone = [...intersections];
if (clone.length !== hits.length || !hits.every(hit => clone.map(e => e.object.uuid).includes(hit.object.uuid))) {
cycle.current = 0;
hits = clone;
renderStatus();
}
// Run custom filter if there is one
if (prev) clone = prev(clone, state);
// Cycle through the actual raycast intersects
for (let i = 0; i < Math.round(cycle.current) % clone.length; i++) {
const first = clone.shift();
clone = [...clone, first];
}
return clone;
}
});
// Cycle, refresh events and render status
const refresh = fn => {
var _get$events$handlers, _get$events$handlers2;
cycle.current = fn(cycle.current);
// Cancel hovered elements and fake a pointer-move
(_get$events$handlers = get().events.handlers) == null || _get$events$handlers.onPointerCancel(undefined);
(_get$events$handlers2 = get().events.handlers) == null || _get$events$handlers2.onPointerMove(lastEvent);
renderStatus();
};
// Key events
const tabEvent = event => {
if ((event.keyCode || event.which) === keyCode) {
if (preventDefault) event.preventDefault();
if (hits.length > 1) refresh(current => current + 1);
}
};
// Wheel events
const wheelEvent = event => {
if (preventDefault) event.preventDefault();
let delta = 0;
if (!event) event = window.event;
if (event.wheelDelta) delta = event.wheelDelta / 120;else if (event.detail) delta = -event.detail / 3;
if (hits.length > 1) refresh(current => Math.abs(current - delta));
};
// Catch last move event and position custom status
const moveEvent = event => lastEvent = event;
document.addEventListener('pointermove', moveEvent, {
passive: true
});
if (scroll) document.addEventListener('wheel', wheelEvent);
if (keyCode !== undefined) document.addEventListener('keydown', tabEvent);
return () => {
// Clean up
setEvents({
filter: prev
});
if (keyCode !== undefined) document.removeEventListener('keydown', tabEvent);
if (scroll) document.removeEventListener('wheel', wheelEvent);
document.removeEventListener('pointermove', moveEvent);
};
}, [gl, get, setEvents, preventDefault, scroll, keyCode]);
return null;
}
export { CycleRaycast };