Spaces:
Running
Running
| import _extends from '@babel/runtime/helpers/esm/extends'; | |
| import * as React from 'react'; | |
| import * as ReactDOM from 'react-dom/client'; | |
| import { useThree, useFrame, context as context$1 } from '@react-three/fiber'; | |
| import { easing } from 'maath'; | |
| const context = /* @__PURE__ */React.createContext(null); | |
| function useScroll() { | |
| return React.useContext(context); | |
| } | |
| function ScrollControls({ | |
| eps = 0.00001, | |
| enabled = true, | |
| infinite, | |
| horizontal, | |
| pages = 1, | |
| distance = 1, | |
| damping = 0.25, | |
| maxSpeed = Infinity, | |
| prepend = false, | |
| style = {}, | |
| children | |
| }) { | |
| const { | |
| get, | |
| setEvents, | |
| gl, | |
| size, | |
| invalidate, | |
| events | |
| } = useThree(); | |
| const [el] = React.useState(() => document.createElement('div')); | |
| const [fill] = React.useState(() => document.createElement('div')); | |
| const [fixed] = React.useState(() => document.createElement('div')); | |
| const target = gl.domElement.parentNode; | |
| const scroll = React.useRef(0); | |
| const state = React.useMemo(() => { | |
| const state = { | |
| el, | |
| eps, | |
| fill, | |
| fixed, | |
| horizontal, | |
| damping, | |
| offset: 0, | |
| delta: 0, | |
| scroll, | |
| pages, | |
| // 0-1 for a range between from -> from + distance | |
| range(from, distance, margin = 0) { | |
| const start = from - margin; | |
| const end = start + distance + margin * 2; | |
| return this.offset < start ? 0 : this.offset > end ? 1 : (this.offset - start) / (end - start); | |
| }, | |
| // 0-1-0 for a range between from -> from + distance | |
| curve(from, distance, margin = 0) { | |
| return Math.sin(this.range(from, distance, margin) * Math.PI); | |
| }, | |
| // true/false for a range between from -> from + distance | |
| visible(from, distance, margin = 0) { | |
| const start = from - margin; | |
| const end = start + distance + margin * 2; | |
| return this.offset >= start && this.offset <= end; | |
| } | |
| }; | |
| return state; | |
| }, [eps, damping, horizontal, pages]); | |
| React.useEffect(() => { | |
| el.style.position = 'absolute'; | |
| el.style.width = '100%'; | |
| el.style.height = '100%'; | |
| el.style[horizontal ? 'overflowX' : 'overflowY'] = 'auto'; | |
| el.style[horizontal ? 'overflowY' : 'overflowX'] = 'hidden'; | |
| el.style.top = '0px'; | |
| el.style.left = '0px'; | |
| for (const key in style) { | |
| el.style[key] = style[key]; | |
| } | |
| fixed.style.position = 'sticky'; | |
| fixed.style.top = '0px'; | |
| fixed.style.left = '0px'; | |
| fixed.style.width = '100%'; | |
| fixed.style.height = '100%'; | |
| fixed.style.overflow = 'hidden'; | |
| el.appendChild(fixed); | |
| fill.style.height = horizontal ? '100%' : `${pages * distance * 100}%`; | |
| fill.style.width = horizontal ? `${pages * distance * 100}%` : '100%'; | |
| fill.style.pointerEvents = 'none'; | |
| el.appendChild(fill); | |
| if (prepend) target.prepend(el);else target.appendChild(el); | |
| // Init scroll one pixel in to allow upward/leftward scroll | |
| el[horizontal ? 'scrollLeft' : 'scrollTop'] = 1; | |
| const oldTarget = events.connected || gl.domElement; | |
| requestAnimationFrame(() => events.connect == null ? void 0 : events.connect(el)); | |
| const oldCompute = get().events.compute; | |
| setEvents({ | |
| compute(event, state) { | |
| // we are using boundingClientRect because we could not rely on target.offsetTop as canvas could be positioned anywhere in dom | |
| const { | |
| left, | |
| top | |
| } = target.getBoundingClientRect(); | |
| const offsetX = event.clientX - left; | |
| const offsetY = event.clientY - top; | |
| state.pointer.set(offsetX / state.size.width * 2 - 1, -(offsetY / state.size.height) * 2 + 1); | |
| state.raycaster.setFromCamera(state.pointer, state.camera); | |
| } | |
| }); | |
| return () => { | |
| target.removeChild(el); | |
| setEvents({ | |
| compute: oldCompute | |
| }); | |
| events.connect == null || events.connect(oldTarget); | |
| }; | |
| }, [pages, distance, horizontal, el, fill, fixed, target]); | |
| React.useEffect(() => { | |
| if (events.connected === el) { | |
| const containerLength = size[horizontal ? 'width' : 'height']; | |
| const scrollLength = el[horizontal ? 'scrollWidth' : 'scrollHeight']; | |
| const scrollThreshold = scrollLength - containerLength; | |
| let current = 0; | |
| let disableScroll = true; | |
| let firstRun = true; | |
| const onScroll = () => { | |
| // Prevent first scroll because it is indirectly caused by the one pixel offset | |
| if (!enabled || firstRun) return; | |
| invalidate(); | |
| current = el[horizontal ? 'scrollLeft' : 'scrollTop']; | |
| scroll.current = current / scrollThreshold; | |
| if (infinite) { | |
| if (!disableScroll) { | |
| if (current >= scrollThreshold) { | |
| const damp = 1 - state.offset; | |
| el[horizontal ? 'scrollLeft' : 'scrollTop'] = 1; | |
| scroll.current = state.offset = -damp; | |
| disableScroll = true; | |
| } else if (current <= 0) { | |
| const damp = 1 + state.offset; | |
| el[horizontal ? 'scrollLeft' : 'scrollTop'] = scrollLength; | |
| scroll.current = state.offset = damp; | |
| disableScroll = true; | |
| } | |
| } | |
| if (disableScroll) setTimeout(() => disableScroll = false, 40); | |
| } | |
| }; | |
| el.addEventListener('scroll', onScroll, { | |
| passive: true | |
| }); | |
| requestAnimationFrame(() => firstRun = false); | |
| const onWheel = e => el.scrollLeft += e.deltaY / 2; | |
| if (horizontal) el.addEventListener('wheel', onWheel, { | |
| passive: true | |
| }); | |
| return () => { | |
| el.removeEventListener('scroll', onScroll); | |
| if (horizontal) el.removeEventListener('wheel', onWheel); | |
| }; | |
| } | |
| }, [el, events, size, infinite, state, invalidate, horizontal, enabled]); | |
| let last = 0; | |
| useFrame((_, delta) => { | |
| last = state.offset; | |
| easing.damp(state, 'offset', scroll.current, damping, delta, maxSpeed, undefined, eps); | |
| easing.damp(state, 'delta', Math.abs(last - state.offset), damping, delta, maxSpeed, undefined, eps); | |
| if (state.delta > eps) invalidate(); | |
| }); | |
| return /*#__PURE__*/React.createElement(context.Provider, { | |
| value: state | |
| }, children); | |
| } | |
| const ScrollCanvas = /* @__PURE__ */React.forwardRef(({ | |
| children | |
| }, ref) => { | |
| const group = React.useRef(null); | |
| React.useImperativeHandle(ref, () => group.current, []); | |
| const state = useScroll(); | |
| const { | |
| width, | |
| height | |
| } = useThree(state => state.viewport); | |
| useFrame(() => { | |
| group.current.position.x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0; | |
| group.current.position.y = state.horizontal ? 0 : height * (state.pages - 1) * state.offset; | |
| }); | |
| return /*#__PURE__*/React.createElement("group", { | |
| ref: group | |
| }, children); | |
| }); | |
| const ScrollHtml = /*#__PURE__*/React.forwardRef(({ | |
| children, | |
| style, | |
| ...props | |
| }, ref) => { | |
| const state = useScroll(); | |
| const group = React.useRef(null); | |
| React.useImperativeHandle(ref, () => group.current, []); | |
| const { | |
| width, | |
| height | |
| } = useThree(state => state.size); | |
| const fiberState = React.useContext(context$1); | |
| const root = React.useMemo(() => ReactDOM.createRoot(state.fixed), [state.fixed]); | |
| useFrame(() => { | |
| if (state.delta > state.eps) { | |
| group.current.style.transform = `translate3d(${state.horizontal ? -width * (state.pages - 1) * state.offset : 0}px,${state.horizontal ? 0 : height * (state.pages - 1) * -state.offset}px,0)`; | |
| } | |
| }); | |
| root.render(/*#__PURE__*/React.createElement("div", _extends({ | |
| ref: group, | |
| style: { | |
| ...style, | |
| position: 'absolute', | |
| top: 0, | |
| left: 0, | |
| willChange: 'transform' | |
| } | |
| }, props), /*#__PURE__*/React.createElement(context.Provider, { | |
| value: state | |
| }, /*#__PURE__*/React.createElement(context$1.Provider, { | |
| value: fiberState | |
| }, children)))); | |
| return null; | |
| }); | |
| const Scroll = /* @__PURE__ */React.forwardRef(({ | |
| html, | |
| ...props | |
| }, ref) => { | |
| const El = html ? ScrollHtml : ScrollCanvas; | |
| return /*#__PURE__*/React.createElement(El, _extends({ | |
| ref: ref | |
| }, props)); | |
| }); | |
| export { Scroll, ScrollControls, useScroll }; | |