darkroomengineering / lenis

How smooth scroll should be
https://lenis.darkroom.engineering
MIT License
8.44k stars 366 forks source link

Using Lenis with NextJS 13 #170

Closed PixelPage-YT closed 6 months ago

PixelPage-YT commented 1 year ago

Describe the bug

Current Workaround If you have resizing issues, consider this variant: https://github.com/studio-freight/lenis/issues/170#issuecomment-1635443119

Normal variant below:

'use client';

import Lenis from '@studio-freight/lenis';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useRef } from 'react';

/**
 * Wrapper for Lenis
 */
export default function SmoothScroller() {
    const lenis = useRef<Lenis | null>(null);
    const pathname = usePathname();
    const searchParams = useSearchParams();

    // Scroll to top if the dependencies change (on pathname/searchParams/lenis change)
    useEffect(() => {
        if (lenis.current) lenis.current!.scrollTo(0, { immediate: true });
    }, [pathname, searchParams, lenis]);

    useEffect(() => {
        const lenis = new Lenis();

        lenis.on('scroll', (e: any) => {
            console.log(e);
        });

        function raf(time: number) {
            lenis.raf(time);
            requestAnimationFrame(raf);
        }

        requestAnimationFrame(raf);

        return () => {
            lenis.destroy();
        };
    }, []);

    return <></>;
}

I'm going to leave this open until it's fixed. https://github.com/developit/microbundle/issues/1049 Also needs to be fixed if we don't want to write 'use client'; on the beginning of the lenis wrapper.

yuxxeun commented 8 months ago

you should instanciate Lenis inside a useEffect, otherwise it's gonna fail on SSR

'use client';

import Lenis from '@studio-freight/lenis';

const SmoothScroller = () => {
    useEffect(()=>{
      const lenis = new Lenis();

      function raf(time: any) {
        lenis.raf(time);
        requestAnimationFrame(raf);
      }

      raf()

      return () => {
        lenis.destroy()
      }
    }, [])
};

export default SmoothScroller;

surprisingly its just work , thanks for this answer

philosofonusus commented 8 months ago

@yuxxeun I wouldn't recommend this solution, it has an issue where scroll doesn't reset on route change and performance-wise tempus does a better job

these are better ways to manage it if you are using app dir

if you want to use lenis instance in your components:

"use client";
import Lenis from "@studio-freight/lenis";
import Tempus from "@studio-freight/tempus";
import router from "next/router";
import { createContext, useContext, useLayoutEffect, useState } from "react";

export const lenisCTX = createContext<Lenis | null>(null);

export const useLenis = () => useContext(lenisCTX);

export default function Lenify({ children }: { children: React.ReactNode }) {
  const [lenis, setLenis] = useState<Lenis | null>(null);

  useLayoutEffect(() => {
    const lenis = new Lenis();

    setLenis(lenis);

    const resize = setInterval(() => {
      lenis.resize();
    }, 150);
    function onFrame(time: number) {
      lenis.raf(time);
    }
    const unsubscribe = Tempus.add(onFrame);

    router.events.on("routeChangeStart", () => {
      lenis.scrollTo(0, { immediate: true });
    });

    return () => {
      unsubscribe();
      clearInterval(resize);
      setLenis(null);
      lenis.destroy();
    };
  }, []);

  return <lenisCTX.Provider value={lenis}>{children}</lenisCTX.Provider>;
}

and If you just want to initiate lenis

"use client";
import Tempus from "@studio-freight/tempus";
import Lenis from "@studio-freight/lenis";
import { useEffect, useLayoutEffect, useRef } from "react";
import { usePathname, useSearchParams } from "next/navigation";

export default function Lenify() {
  const lenis = useRef<Lenis | null>(null);
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    if (lenis.current) lenis.current!.scrollTo(0, { immediate: true });
  }, [pathname, searchParams, lenis]);

  useLayoutEffect(() => {
    lenis.current = new Lenis();

    const resize = setInterval(() => {
      lenis.current!.resize();
    }, 150);
    function onFrame(time: number) {
      lenis.current!.raf(time);
    }
    const unsubscribe = Tempus.add(onFrame);

    return () => {
      unsubscribe();
      clearInterval(resize);
      lenis.current!.destroy();
      lenis.current = null;
    };
  }, []);
  return null;
}
yuxxeun commented 8 months ago

@yuxxeun I wouldn't recommend this solution, it has an issue where scroll doesn't reset on route change and performance-wise tempus does a better job

these are better ways to manage it if you are using app dir

if you want to use lenis instance in your components:

"use client";
import Lenis from "@studio-freight/lenis";
import Tempus from "@studio-freight/tempus";
import router from "next/router";
import { createContext, useContext, useLayoutEffect, useState } from "react";

export const lenisCTX = createContext<Lenis | null>(null);

export const useLenis = () => useContext(lenisCTX);

export default function Lenify({ children }: { children: React.ReactNode }) {
  const [lenis, setLenis] = useState<Lenis | null>(null);

  useLayoutEffect(() => {
    const lenis = new Lenis();

    setLenis(lenis);

    const resize = setInterval(() => {
      lenis.resize();
    }, 150);
    function onFrame(time: number) {
      lenis.raf(time);
    }
    const unsubscribe = Tempus.add(onFrame);

    router.events.on("routeChangeStart", () => {
      lenis.scrollTo(0, { immediate: true });
    });

    return () => {
      unsubscribe();
      clearInterval(resize);
      setLenis(null);
      lenis.destroy();
    };
  }, []);

  return <lenisCTX.Provider value={lenis}>{children}</lenisCTX.Provider>;
}

and If you just want to initiate lenis

"use client";
import Tempus from "@studio-freight/tempus";
import Lenis from "@studio-freight/lenis";
import { useEffect, useLayoutEffect, useRef } from "react";
import { usePathname, useSearchParams } from "next/navigation";

export default function Lenify() {
  const lenis = useRef<Lenis | null>(null);
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    if (lenis.current) lenis.current!.scrollTo(0, { immediate: true });
  }, [pathname, searchParams, lenis]);

  useLayoutEffect(() => {
    lenis.current = new Lenis();

    const resize = setInterval(() => {
      lenis.current!.resize();
    }, 150);
    function onFrame(time: number) {
      lenis.current!.raf(time);
    }
    const unsubscribe = Tempus.add(onFrame);

    return () => {
      unsubscribe();
      clearInterval(resize);
      lenis.current!.destroy();
      lenis.current = null;
    };
  }, []);
  return null;
}

ah okey i'll try it soon, tbh, i don't realize this package have an issue (scroll doesn't reset on route change). thanks for giving me better way to use Lenis package with Next.js (app dir).

clementroche commented 6 months ago

Our starter Satus is now Next.js App based, it should solve most of issues you had. I'm closing this issue and let you open new ones if you need to.