darkroomengineering / lenis

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

prevent scrolling if html has style overflow: hidden dynamically #194

Closed ahmetsali closed 11 months ago

ahmetsali commented 1 year ago

Describe the bug In some cases such as lightbox plugins or modal boxes, when the modal box is visible the plugins set overflow: hidden; to html tag to prevent scrolling of the body on the background because the modal is active. Lenis.js doesn't respond to dynamically changes of overflow property.

To Reproduce Try using Lenis.js with a lightbox plugin such as this; https://dimsemenov.com/plugins/magnific-popup/

Thanks for this great plugin.

ahmetsali commented 1 year ago

Update: I tried to achieve this with this code, it works but it has really poor performance, not usable.

const html = document.documentElement;

// typically for <body> and <html>
function isOverflowHidden(el) {
  var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
  return (overflow === 'hidden');
}

lenis.on('scroll', ({ scroll, limit, velocity, direction, progress }) => {
    if (isOverflowHidden(html)) {
      lenis.stop();
    } else {
      lenis.start();
    }

  })
clementroche commented 1 year ago

Hi, i agree that's would be ideal, however there is currently no performant way to do this.

SiddharthSham commented 1 year ago

Two notes here,

  1. It would be necessary to look for overflow: clip as well
  2. This could be possible through MutationObserver perhaps? Observe for changes to the style attribute and/or the classList and trigger a lock if we match the required conditions.
clementroche commented 1 year ago

@ahmetsali @SiddharthSham, i gave it a shot, you can test with version 1.0.18-0. It works better than i initially thought, it just doens't trigger if you check/uncheck overflow property from devtool. I'm gonna wait for some feedbacks before include it in the codebase because getComputedStyle can be hazadous sometimes.

https://github.com/studio-freight/lenis/tree/observe-overflow

SiddharthSham commented 1 year ago

One note regarding perf, getComputedStyle does not need to be triggered on each call to check() since the value returned by getComputedStyle is a reference to a live object which will automatically update.

SiddharthSham commented 1 year ago

While I was curious to see how this would work, I'm not sure if this will always be the best approach. I understand using overflow: hidden does not work perfectly across different platforms and also causes a layout shift by showing/hiding the scrollbar.

For this reason, I generally use the position: fixed hack which is a bit more work but works better imo. Some reference work here, by body-scroll-lock package.

Perhaps this could be a plugin/extension which doesn't add weight to the core lenis lib? That way we can choose to include this behaviour only if required. 🤔

clementroche commented 1 year ago

1.0.18-1 should be more robust

Cuzart commented 11 months ago

I am facing a similar problem. When using an modal I expect a scroll lock for the page but even manually setting the overflow for to hidden does not stop lenis-scrolling (tried with latest version and with 1.0.18-1). Is there any progress on this to be expected because for this is a crucial criteria.

clementroche commented 11 months ago

This is not planned to be part of Lenis for performance reason, you have to call lenis.stop() manually.

You can still implement it by yourself using this snippet.

const lenis = new Lenis()

function raf(time) {

 const isModalOpened = // check if you modal is here

 if(isModalOpened) {
   lenis.stop()
 } else {
   lenis.start()
 }

  lenis.raf(time)
  requestAnimationFrame(raf)
}

requestAnimationFrame(raf)
tamalCodes commented 10 months ago

Hi @clementroche , i am facing the same issue for modals. Here is the code I am using. The issue is even if the isScrollAllowed becomes false I cannot scroll through the modal. However, I can do that if I remove the lenis code entirely.

Video

https://github.com/studio-freight/lenis/assets/72851613/5c216381-1d4c-4bb1-9019-c523f69dc166

Codes

I have Home.jsx where all the components are there . . . .

import React, { useRef } from "react";
import Lenis from "@studio-freight/lenis";
import { useEffect } from "react";
import Work from "../components/work/Work";
import { useScrollUtils } from "../Store";

const Home = () => {
  const { isScrollAllowed, setIsScrollAllowed } = useScrollUtils();

  useEffect(() => {
    console.log(isScrollAllowed); // I get "false" as an answer correctly, when I change the value
    const lenis = new Lenis({
      duration: 3,
    });

    function raf(time) {
      if (isScrollAllowed) lenis.start();
      else lenis.stop();
      lenis.raf(time);
      requestAnimationFrame(raf);
    }

    requestAnimationFrame(raf);
  }, [isScrollAllowed]);
  return (
    <div style={{ position: "relative" }}>
      <Work />
    </div>
  );
};

export default Home;

Inside the WorkCard.jsx component I have the card, where I am able to Open a modal. I have omitted extra jsx code to save your time.


import React, { useRef } from "react";
import { motion, useInView } from "framer-motion";
import { useScrollUtils } from "../../Store";

const WorkCard = ({ isWorkModalOpen, setWorkModalOpen }) => {

const { isScrollAllowed, setIsScrollAllowed } = useScrollUtils();

  return (
    <div className="workcard_container">
      <motion.div>

       <button
          onClick={() => {
            setIsScrollAllowed(false);
            setWorkModalOpen(true);
          }}
        >
          See my work
        </button>

      </motion.div>
    </div>
  );
};

export default WorkCard;
esiao commented 7 months ago

I had the same issue and managed to fix it by using .destroy() and re-creating the Lenis instance. I'm in a Nuxt (Vue.js) context and .stop() wouldn't allow the scroll inside of the modal generated by a 3rd party script on Chrome. Leaving this here in case someone faces the same issue:

onMounted(() => {
  lenis.value = new Lenis({})

  function raf(time:number) {
    const isOverflowHidden = document.body.style.overflowY === 'hidden'

    if (isOverflowHidden && lenis.value) {
      lenis.value.destroy()
      lenis.value = null;
    }
    else if (!isOverflowHidden && !lenis.value) {
      lenis.value = new Lenis({})
    }

    lenis.value?.raf(time)
    requestAnimationFrame(raf)
  }

  requestAnimationFrame(raf)
})
1ucay commented 3 months ago

@esiao nice work!