How to stop scroll on modal pop-up & keep smooth scroll active? #292
-
👋 Any help would be greatly appreciated, thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 11 comments 29 replies
-
You shouldn't use the data attributes for that purpose. There are two methods described here. openModal() {
// Your modal open code
lenis.stop()
}
closeModal() {
// Your modal close code
lenis.start()
} |
Beta Was this translation helpful? Give feedback.
-
oke |
Beta Was this translation helpful? Give feedback.
-
Thanks for the reply! Unfortunately the link is broken. I'm using the data-lenis-stop attribute on modal open, and data-lenis-start on modal close, however my modal itself has overflow behaviour, so I'm using data-lenis-prevent to keep scroll active on the modal itself. |
Beta Was this translation helpful? Give feedback.
-
I'm having a similar issue with a drawer. I have ReactLenis activated for the root scroll in my NextJS layout. When I open the drawer, I would like the smooth scroll on the root to stop and smooth scroll on the drawer to be active. As it stands, I am able to stop root scrolling via data-lenis-prevent, but the smooth scroll on the drawer does not work. Is this currently possible ? |
Beta Was this translation helpful? Give feedback.
-
Check Example using |
Beta Was this translation helpful? Give feedback.
-
@kipedia please share a reproduction link |
Beta Was this translation helpful? Give feedback.
-
Any other solutions? I got many dialogs, popups, alerts, etc. in my app, it seems not quite right if I need to use I'm using Radix as well as Shadcn, after dig into the code base of one, I see that radix uses import { RemoveScroll } from "react-remove-scroll";
...
const ScrollLockWrapper = disableOutsideScroll ? RemoveScroll : React.Fragment; The problem that Lenis someway override this behavior. How to make Lenis respect that? |
Beta Was this translation helpful? Give feedback.
-
I faced a similar issue with my mobile navigation. I needed to prevent the document from scrolling when it opened. Otherwise, some event listeners inside the nav wouldn't properly register (mostly click events). Anyway, here's a very basic JS snippet that you can tailor to your own needs: document.addEventListener('DOMContentLoaded', () => {
const pageWrapper = document.querySelector('html');
const hamburger = document.querySelector('#hamburger');
let navIsOpen = false;
hamburger.addEventListener('click', () => {
navIsOpen = !navIsOpen;
pageWrapper.style.height = navIsOpen ? '100svh' : '';
pageWrapper.style.overflow = navIsOpen ? 'hidden' : '';
if (navIsOpen){
lenis.stop();
pageWrapper.setAttribute("data-lenis-prevent", "true");
}else{
lenis.start();
pageWrapper.removeAttribute("data-lenis-prevent");
}
});
}); |
Beta Was this translation helpful? Give feedback.
-
One approach I found for my use case is to track the state of the data-scroll-locked property on the body element using a MutationObserver. "use client";
import { useEffect, useRef } from "react";
import { cancelFrame, frame, type frameData } from "framer-motion";
import { type LenisRef, ReactLenis } from "lenis/react";
type ReactLenisProviderProps = {
children: React.ReactNode;
};
const ReactLenisProvider = ({ children }: ReactLenisProviderProps) => {
const lenisRef = useRef<LenisRef | null>(null);
useEffect(() => {
function update(time: typeof frameData) {
lenisRef.current?.lenis?.raf(time.timestamp);
}
frame.update(update, true);
const observer = new MutationObserver(() => {
const isScrollLocked = document.body.dataset.scrollLocked === "1";
const bodyStyle = document.body.style;
if (lenisRef.current?.lenis) {
if (isScrollLocked) {
lenisRef.current.lenis.stop();
bodyStyle.overflow = "hidden";
} else {
lenisRef.current.lenis.start();
bodyStyle.overflow = "";
}
}
});
observer.observe(document.body, {
attributes: true,
attributeFilter: ["data-scroll-locked"],
});
return () => {
cancelFrame(update);
observer.disconnect();
};
}, []);
return (
<ReactLenis
root
options={{
autoRaf: false,
duration: 1.5,
lerp: 0.1,
orientation: "vertical",
gestureOrientation: "vertical",
}}
ref={lenisRef}
>
{children}
</ReactLenis>
);
};
export { ReactLenisProvider }; And also add the data-lenis-prevent attribute to the element. For me, it was the ShadCN components (Dialog, Sheet, and DropdownMenu) content. I added the attribute globally within the respective component itself. |
Beta Was this translation helpful? Give feedback.
-
Simplest solution is to create a wrapper around the base modal component you are using and listening to an event handler. export function ModalRoot({
children,
onOpenChange,
...props
}: DialogRootProps) {
const lenis = useLenis();
return (
<DialogRoot
lazyMount
unmountOnExit
preventScroll={false}
onOpenChange={({ open }) => {
if (open) {
lenis?.stop();
} else {
lenis?.start();
}
onOpenChange?.({ open });
}}
{...props}
>
{children}
</DialogRoot>
);
} Then inside the ModalContent initialise another instance of lenis (non root) to have smooth scroll for the content inside. function ModalContent({
children,
positionerClassName,
contentClassName,
}: ContentProps) {
return (
<Dialog.Content>
<Lenis root={false}>
{children}
</Lenis>
</Dialog.Content>
);
} This approach assumes you are using a modular component library(you definetly should) which grants you access to the single building blocks of a modal/dialog. |
Beta Was this translation helpful? Give feedback.
-
"use client"; import React from "react"; type Props = { const TermsConditions = ({ onClose }: Props) => { <div onClick={(e) => e.stopPropagation()} data-lenis-prevent className="bg-white overflow-y-auto max-h-[90vh] shadow-lg rounded-2xl w-full max-w-4xl " > {/* Header */} Terms And ConditionsYour Agreement
); export default TermsConditions; |
Beta Was this translation helpful? Give feedback.
Check
prevent
option #347Example using
prevent
optionExample using
wrapper
andcontent
options