UNPKG

fullpage-scroll-component

Version:
329 lines (316 loc) 8.99 kB
import { jsx, Fragment, jsxs } from 'react/jsx-runtime'; import { useRef, useState, useCallback, useMemo, createContext, useContext, useEffect, forwardRef, useImperativeHandle, useId } from 'react'; import debounce from 'lodash/debounce'; import styled from '@emotion/styled'; const useSuccessiveValue = ({ defaultNumber = 0, maximum }) => { const defaultCurrentNumberRef = useRef(defaultNumber !== null && defaultNumber !== void 0 ? defaultNumber : 0); const [current, setCurrent] = useState(defaultCurrentNumberRef.current); const resetCurrent = useCallback(() => { setCurrent(defaultCurrentNumberRef.current); }, []); const next = useCallback(() => { if (current >= maximum) { return current; } setCurrent(prev => ++prev); return current + 1; }, [current, maximum]); const prev = useCallback(() => { if (current <= 0) { return current; } setCurrent(prev => --prev); return current - 1; }, [current]); const hasNext = useMemo(() => { return current < maximum; }, [current, maximum]); const hasPrev = useMemo(() => { return current > 0; }, [current]); const move = useCallback(to => { if (to >= 0 || to <= maximum) { setCurrent(to); } }, [maximum]); return { current, setCurrent, resetCurrent, next, prev, hasNext, hasPrev, move }; }; const StepScrollContext = createContext(null); StepScrollContext.displayName = "StepScroll"; const useStepScroll = () => { const context = useContext(StepScrollContext); const { currentPage, hasNextPage, hasPrevPage, nextPage, prevPage, movePage, pagesIdArray, setPagesIdArray } = context; return { currentPage, hasNextPage, hasPrevPage, nextPage, prevPage, movePage, pagesIdArray, setPagesIdArray }; }; function useDebouncedScrollDirection({ upScrollCallback, downScrollCallback, isPreventDefault, debounceDelay = 1000 }) { const [scrollDir, setScrollDir] = useState(""); const [touchStart, setTouchStart] = useState(0); useEffect(() => { const debouncedScroll = debounce(direction => { if (direction === "down") { if (downScrollCallback && isPreventDefault) { downScrollCallback(); } } if (direction === "up") { if (upScrollCallback && isPreventDefault) { upScrollCallback(); } } }, debounceDelay); const handleScroll = e => { if (isPreventDefault) { e.preventDefault(); } if (e.deltaY > 0) { debouncedScroll("down"); setScrollDir("down"); } else if (e.deltaY < 0) { debouncedScroll("up"); setScrollDir("up"); } }; const onSetTouchStart = e => { setTouchStart(e.changedTouches[0].clientY); }; const handleMobileScroll = e => { if (isPreventDefault) { e.preventDefault(); } if (e.changedTouches[0].clientY < touchStart) { debouncedScroll("down"); setScrollDir("down"); } else if (e.changedTouches[0].clientY > touchStart) { debouncedScroll("up"); setScrollDir("up"); } }; window.addEventListener("wheel", handleScroll, { passive: false }); window.addEventListener("touchmove", handleMobileScroll, { passive: false }); window.addEventListener("touchstart", onSetTouchStart, { passive: false }); return () => { window.removeEventListener("wheel", handleScroll); window.removeEventListener("touchmove", handleMobileScroll); window.removeEventListener("touchstart", onSetTouchStart); }; }, [upScrollCallback, downScrollCallback, touchStart]); return scrollDir; } const useDidUpdate = (callback, deps) => { const mounted = useRef(false); useEffect(() => { if (!mounted.current) { mounted.current = true; } else { callback(); } }, deps); }; function DebouncedScroll({ isPreventDefault = true, delay = 300 }) { const { prevPage, nextPage } = useStepScroll(); useDebouncedScrollDirection({ upScrollCallback: prevPage, downScrollCallback: nextPage, isPreventDefault: isPreventDefault, debounceDelay: delay }); return jsx(Fragment, {}); } const StepScroll = forwardRef(function ({ children, defaultPage = 0, delay = 300, isPreventDefault = true, isScrollabled = true, isCaptured = true }, ref) { const [pagesIdArray, setPagesIdArray] = useState([]); const { current, setCurrent, resetCurrent, next, prev, hasNext, hasPrev, move } = useSuccessiveValue({ maximum: children.length - 1, defaultNumber: defaultPage >= children.length ? 0 : defaultPage }); const providerValues = useMemo(() => ({ pagesIdArray, setPagesIdArray, currentPage: current, setCurrentPage: setCurrent, resetCurrent: resetCurrent, hasNextPage: hasNext, hasPrevPage: hasPrev, nextPage: next, prevPage: prev, movePage: move }), [pagesIdArray, setPagesIdArray, current, setCurrent, resetCurrent, hasNext, hasPrev, next, prev, move]); useImperativeHandle(ref, () => ({ currentPage: current, nextPage: next, prevPage: prev, movePage: move, resetCurrentPage: resetCurrent })); const handleScroll = e => { e.preventDefault(); }; const handleMobileScroll = e => { e.preventDefault(); }; useEffect(() => { if (!isScrollabled) { window.addEventListener("wheel", handleScroll, { passive: false, capture: isCaptured }); window.addEventListener("touchmove", handleMobileScroll, { passive: false, capture: isCaptured }); return () => { window.removeEventListener("wheel", handleScroll); window.removeEventListener("touchmove", handleMobileScroll); }; } }, [isCaptured, isScrollabled]); return jsxs(StepScrollContext.Provider, Object.assign({ value: providerValues }, { children: [isScrollabled && jsx(DebouncedScroll, { isPreventDefault: isPreventDefault, delay: delay }), children] })); }); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } const PageContainer = styled.div` width: 100vw; height: 100vh; `; /** * @description * StepScroll 컴포넌트의 children으로만 사용할 수 있는 컴포넌트 입니다. */ function Page(_a) { var { children } = _a, args = __rest(_a, ["children"]); const { pagesIdArray, setPagesIdArray, currentPage } = useStepScroll(); const ref = useRef(null); const id = useId(); useEffect(() => { if (!pagesIdArray.includes(id)) { setPagesIdArray(prev => [...prev, id]); } }, []); useDidUpdate(() => { var _a; if (ref.current && pagesIdArray[currentPage] === id) { (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: "start", behavior: "smooth" }); } }, [pagesIdArray, currentPage]); useEffect(() => { const scrollIntoViewByResize = () => { var _a; if (ref.current && pagesIdArray[currentPage] === id) { (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: "start" }); } }; window.addEventListener("resize", scrollIntoViewByResize); return () => { window.removeEventListener("resize", scrollIntoViewByResize); }; }, [ref, currentPage, pagesIdArray, id]); return jsx(PageContainer, Object.assign({ id: id, ref: ref }, args, { children: children })); } export { Page, StepScroll, StepScrollContext, useDebouncedScrollDirection, useDidUpdate, useStepScroll, useSuccessiveValue };