diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..2ac3040 --- /dev/null +++ b/index.d.ts @@ -0,0 +1 @@ +// declare module 'gsap/dist/ScrollSmoother' diff --git a/package.json b/package.json index b7dc47e..d6d5520 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@juggle/resize-observer": "^3.3.1", "@radix-ui/react-polymorphic": "^0.0.14", "clsx": "^1.1.1", - "gsap": "https://basement.studio/gsap/bonus-0.0.12.tgz", + "gsap": "./src/lib/gsap/gsap-bonus.tgz", "keen-slider": "^6.6.5", "locomotive-scroll": "^4.1.4", "next": "12.1.3", diff --git a/src/components/layout/page.tsx b/src/components/layout/page.tsx index 77959fa..7118ced 100644 --- a/src/components/layout/page.tsx +++ b/src/components/layout/page.tsx @@ -2,12 +2,12 @@ import * as React from 'react' import { Footer } from '~/components/common/footer' import { Header } from '~/components/common/header' -import { LocomotiveScrollProvider } from '~/lib/locomotive-scroll/provider' +import { CUSTOM_EASE, DURATION, ScrollSmoother } from '~/lib/gsap' type Props = { children?: React.ReactNode extras?: React.ReactNode - locomotiveScroll?: boolean + smoothScroll?: boolean } const ContentMemo = React.memo(({ children, extras }: Props) => { @@ -22,11 +22,26 @@ const ContentMemo = React.memo(({ children, extras }: Props) => { }) export const PageLayout = (props: Props) => { - if (props.locomotiveScroll) { + if (props.smoothScroll) { + const contentRef = React.useRef(null) + const wrapperRef = React.useRef(null) + React.useEffect(() => { + ScrollSmoother.create({ + content: contentRef.current, + effects: true, + ignoreMobileResize: true, + normalizeScroll: true, + smooth: DURATION * 2, + ease: CUSTOM_EASE, + wrapper: wrapperRef.current + }) + }, []) return ( - - - +
+
+ +
+
) } else { return diff --git a/src/hooks/use-device-detect.ts b/src/hooks/use-device-detect.ts index aca440a..9ef5274 100644 --- a/src/hooks/use-device-detect.ts +++ b/src/hooks/use-device-detect.ts @@ -24,11 +24,10 @@ export const useDeviceDetect = () => { React.useEffect(() => { set({ isDesktop: ReactDeviceDetect.isDesktop, - // window.isMobileOrTablet comes from locomotive-scroll - isMobile: window.isMobileOrTablet ?? ReactDeviceDetect.isMobile, + isMobile: ReactDeviceDetect.isMobile, isMobileOnly: ReactDeviceDetect.isMobileOnly, isMobileSafari: ReactDeviceDetect.isMobileSafari, - isTablet: window.isMobileOrTablet ?? ReactDeviceDetect.isTablet, + isTablet: ReactDeviceDetect.isTablet, isChrome: ReactDeviceDetect.isChrome, isFirefox: ReactDeviceDetect.isFirefox, isSafari: ReactDeviceDetect.isSafari, diff --git a/src/hooks/use-scroll-listener.ts b/src/hooks/use-scroll-listener.ts index a2dc3f8..4795d74 100644 --- a/src/hooks/use-scroll-listener.ts +++ b/src/hooks/use-scroll-listener.ts @@ -1,39 +1,20 @@ -import { EventHandler } from 'locomotive-scroll' import * as React from 'react' -import { useLocomotiveScroll } from '~/lib/locomotive-scroll/provider' - export type ScrollListenerHandlers = { - smoothHandler: EventHandler nativeHandler: (e: Event) => void } export const useScrollListener = ({ - smoothHandler, nativeHandler }: ScrollListenerHandlers) => { - const { scroll, isSmooth } = useLocomotiveScroll() - React.useEffect(() => { - if (!scroll) return - if (isSmooth) { - const handler: EventHandler = (args) => { - smoothHandler(args) - } - - scroll.on('scroll', handler) - return () => { - scroll.off('scroll', handler) - } - } else { - const handler = (event: Event) => { - nativeHandler(event) - } - - window.addEventListener('scroll', handler) - return () => { - window.removeEventListener('scroll', handler) - } + const handler = (event: Event) => { + nativeHandler(event) } - }, [isSmooth, nativeHandler, scroll, smoothHandler]) + + window.addEventListener('scroll', handler) + return () => { + window.removeEventListener('scroll', handler) + } + }, [nativeHandler]) } diff --git a/src/hooks/use-timeline-effect.ts b/src/hooks/use-timeline-effect.ts index c80aa0a..3598740 100644 --- a/src/hooks/use-timeline-effect.ts +++ b/src/hooks/use-timeline-effect.ts @@ -1,7 +1,6 @@ import * as React from 'react' import { gsap } from '~/lib/gsap' -import { useLocomotiveScroll } from '~/lib/locomotive-scroll/provider' import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect' @@ -30,13 +29,11 @@ export const useTimelineEffect = ( dependencies: React.DependencyList, options?: { autoKill?: boolean; autoPlay?: boolean } ) => { - const { isReady } = useLocomotiveScroll() const [timeline] = React.useState(() => gsap.timeline({ paused: options?.autoPlay ? false : true }) ) useIsomorphicLayoutEffect(() => { - if (!isReady) return const cleanup = callback?.(timeline) return () => { cleanup?.() @@ -44,7 +41,7 @@ export const useTimelineEffect = ( timeline.kill() } } - }, [isReady, callback, timeline, options, ...(dependencies || [])]) + }, [callback, timeline, options, ...(dependencies || [])]) return { timeline } } diff --git a/src/lib/gsap/gsap-bonus.tgz b/src/lib/gsap/gsap-bonus.tgz new file mode 100644 index 0000000..ec81229 Binary files /dev/null and b/src/lib/gsap/gsap-bonus.tgz differ diff --git a/src/lib/gsap/index.tsx b/src/lib/gsap/index.tsx index 918c8ea..22e0639 100644 --- a/src/lib/gsap/index.tsx +++ b/src/lib/gsap/index.tsx @@ -1,9 +1,17 @@ import gsap from 'gsap' import { CSSRulePlugin } from 'gsap/dist/CSSRulePlugin' import { CustomEase } from 'gsap/dist/CustomEase' +import { ScrollSmoother } from 'gsap/dist/ScrollSmoother' +import { ScrollTrigger } from 'gsap/dist/ScrollTrigger' import { SplitText } from 'gsap/dist/SplitText' -gsap.registerPlugin(CSSRulePlugin, CustomEase, SplitText) +gsap.registerPlugin( + CSSRulePlugin, + CustomEase, + ScrollSmoother, + ScrollTrigger, + SplitText +) const GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2 const RECIPROCAL_GR = 1 / GOLDEN_RATIO @@ -148,4 +156,12 @@ gsap.registerEffect({ } }) -export { CSSRulePlugin, CUSTOM_EASE, DURATION, gsap, SplitText } +export { + CSSRulePlugin, + CUSTOM_EASE, + DURATION, + gsap, + ScrollSmoother, + ScrollTrigger, + SplitText +} diff --git a/src/lib/locomotive-scroll/provider.tsx b/src/lib/locomotive-scroll/provider.tsx deleted file mode 100644 index 270c3f7..0000000 --- a/src/lib/locomotive-scroll/provider.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import type { Scroll } from 'locomotive-scroll' -import * as React from 'react' -import mergeRefs from 'react-merge-refs' - -import { useMeasure } from '~/hooks/use-measure' - -import { LocomotiveScrollScripts } from './scripts' - -// Scroll lerp value -export const lerpScroll = 0.09708 - -export interface Context { - /** - * LocomotiveScroll instance - */ - scroll: Scroll | null - /** - * If LocomotiveScroll is mounted - */ - isReady: boolean - /** - * If isMobile, isSmooth will be false and native behaviour will kick in. - */ - isSmooth: boolean | undefined -} - -const LocomotiveScrollContext = React.createContext( - undefined -) - -type Props = { - children?: React.ReactNode -} - -export const LocomotiveScrollProvider = ({ children }: Props) => { - const [isReady, setIsReady] = React.useState(false) - const locomotiveScrollRef = React.useRef(null) - const scrollContainerRef = React.useRef(null) - const [ref, { height, width }] = useMeasure({ debounce: 100 }) - const [isSmooth, setIsSmooth] = React.useState() - - React.useEffect(() => { - ;(async () => { - try { - const isMobileOrTablet = window.isMobileOrTablet - - if (isMobileOrTablet) { - setIsSmooth(false) - return - } - - if (!scrollContainerRef.current) { - setIsSmooth(false) - return - } - - const LocomotiveScroll = (await import('locomotive-scroll')).default - - const locoScroll = new LocomotiveScroll({ - el: scrollContainerRef.current, - smooth: true, - lerp: lerpScroll, - firefoxMultiplier: 100 - }) - - locomotiveScrollRef.current = locoScroll - setIsSmooth(true) - } catch (e) { - console.error(e) - } finally { - setIsReady(true) // Re-render the context - } - })() - - return () => { - locomotiveScrollRef.current?.destroy() - } - }, []) - - React.useEffect(() => { - locomotiveScrollRef.current?.update() - }, [height, width]) - - return ( - -
- - {children} -
-
- ) -} - -export const useLocomotiveScroll = () => { - const ctx = React.useContext(LocomotiveScrollContext) - if (ctx === undefined) { - throw new Error('useLocomotiveScroll: Context not found') - } - return ctx -} diff --git a/src/lib/locomotive-scroll/scripts.tsx b/src/lib/locomotive-scroll/scripts.tsx deleted file mode 100644 index e7a3dc1..0000000 --- a/src/lib/locomotive-scroll/scripts.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable no-useless-escape */ -import Script from 'next/script' -import * as React from 'react' - -export const tabletBreakpoint = 1024 - -declare global { - interface Window { - isMobileOrTablet: boolean - } -} - -export const LocomotiveScrollScripts = React.memo(() => ( - <> -