javascript - Disable scroll but without hiding the scroll bar for React JS? - Stack Overflow

I'm using next JS for my application. I have a sign in modal for my application, and I would like

I'm using next JS for my application. I have a sign in modal for my application, and I would like to fix the under lying page to not scroll while it is open. I could solve the issue with document.body.style.overflow = 'hidden' when the modal is being open, but then the website jumps to occupy the space the scroll was present.

I would like to preserve the scroll bar yet disable the scroll. I'm calling the modal in a ponent, so CSS properties like overflow:hidden only works on the respective ponent. Is there a way I could achieve whatever I'm trying to perform?

I'm using next JS for my application. I have a sign in modal for my application, and I would like to fix the under lying page to not scroll while it is open. I could solve the issue with document.body.style.overflow = 'hidden' when the modal is being open, but then the website jumps to occupy the space the scroll was present.

I would like to preserve the scroll bar yet disable the scroll. I'm calling the modal in a ponent, so CSS properties like overflow:hidden only works on the respective ponent. Is there a way I could achieve whatever I'm trying to perform?

Share Improve this question asked Jul 12, 2021 at 8:56 Aadhit ShanmugamAadhit Shanmugam 5193 gold badges14 silver badges25 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

Phew. It took many hours of digging (bloggers and A.I. [NOT StackOverflow answers per se, especially not the accepted one here] seem weirdly content with the annoyingly-widespread solution of simply making the scrollbar temporarily disappear entirely, which I think is tacky/distracting), but I finally piled the 3 required ponents to disable the scrollbar while keeping it visible [only tested in Chromium(Edge) and the mobile emulator in Dev Tools]:

Block ScrollWheel Scroll

...
function App(){
  const [isModalOpen, setIsModalOpen] = useState(false)

  useEffect(() => {
    const handleWindowWheel = (event: WheelEvent) => {
      if (isModalOpen){
        event.preventDefault();
      }
    };
    
    window.addEventListener('wheel', handleWindowWheel, { passive: false });
    
    return () => {
      window.removeEventListener('wheel', handleWindowWheel);
    };
  }, [isModalOpen]);

  return (
    ...
    <button onClick={() => setIsModalOpen(true)}>Open Modal</button>
    ...
  )
}

Block Click-and-Drag Scroll

...
function disableScroll() {
  // Store the current X & Y scroll positions.
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;

  // If any scroll event is attempted on the window, change the window's 
  // onscroll function to always scroll back to the saved X & Y positions.
  window.onscroll = function() {
    window.scrollTo(scrollLeft, scrollTop);
  };
}
function enableScroll() {
  // Reset the window's onscroll function.
  window.onscroll = function() {};
}

function App(){
  const [isModalOpen, setIsModalOpen] = useState(false)

  useEffect(() => {
    if (isModalOpen) {
      disableScroll();
    } else {
      enableScroll();
    }
  }, [isModalOpen]);

  return (
    ...
    <button onClick={() => setIsModalOpen(true)}>Open Modal</button>
    ...
  )
}

Credit

The above snippet semi-blocks ScrollWheel scroll too, but it's ugly: It allows you to scroll the scrollbar with the wheel a whole ScrollWheel-click's distance, then visibly snaps the scrollbar back to where it originally was. (Which is why it's remended to additionally implement the Block ScrollWheel Scroll as well.)

Block Finger Scroll (mobile)

...
function App(){
  const [isModalOpen, setIsModalOpen] = useState(false)
  ...
  return (
    {/* Sets the 'touch-action' CSS property to 'none' on 
    the outermost div when the modal is open. */}
    <div style={{ touchAction: isModalOpen ? 'none' : 'auto' }}>
      ...
      <button onClick={() => setIsModalOpen(true)}>Open Modal</button>
      ...
    </div>
  )
}

Combined (Functional Demo):

const {useState} = React;
const {useEffect} = React;

function disableScroll() {
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;

  window.onscroll = function() {
    window.scrollTo(scrollLeft, scrollTop);
  };
}

function enableScroll() {
  window.onscroll = function() {};
}

const ExampleComponent = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  useEffect(() => {
    if (isModalOpen) {
      disableScroll();
    } else {
      enableScroll();
    }

    const handleWindowWheel = (event: WheelEvent) => {
      if (isModalOpen){
        event.preventDefault();
      }
    };
    
    window.addEventListener('wheel', handleWindowWheel, { passive: false });
    
    return () => {
      window.removeEventListener('wheel', handleWindowWheel);
    };
  }, [isModalOpen]);  

  return (
    <div className={isModalOpen ? 'disable-touch-scroll' : ''}>
        {isModalOpen &&
          <div id="modal">
            <span>You shouldn't be able to scroll now.</span>
            <button
              onClick={() => setIsModalOpen(false)}
            >
              Close Modal
            </button>
          </div>
        }
      <div>
        {"hello ".repeat(1000)}
      </div>
      <button 
        id="modal-open-button" 
        onClick={() => setIsModalOpen(true)}
      >
        Open Modal
      </button>
    </div>
  );
};

ReactDOM.createRoot(
    document.getElementById("root")
).render(
    <ExampleComponent/>
);
#modal {
  width: 50%;
  height: 50%;
  position: fixed;
  z-index: 999;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  background: white;
  border: 1px solid black;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

#modal-open-button {
  position: fixed;
  top: 50%;
  color: white;
  background-color: red;
}

.disable-touch-scroll {
  touch-action: none;
}
<script src="https://cdnjs.cloudflare./ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

<div id="root"></div>

You could just add an event to the onscroll, and then keep a track of the current scrollTop, scrollLeft (if you need to handle horizontal too), and then when you don't want scroll just reset the scroll to these stored values.

eg. If you run the snippet below when the large checkbox is checked, scrolling is disabled.

const div = document.querySelector('div');
const cb = document.querySelector('input');

let lastY = 0;

div.innerText =  new Array(3000).fill().map(m => 'hello ').join(' ');

div.addEventListener('scroll', (e) => {
  if (cb.checked) {
    e.target.scrollTop = lastY;
  } else lastY = e.target.scrollTop;
});


cb.addEventListener('click', () => {
  console.log('click');
});
div {
  height: 150px;
  overflow: auto;
}
input {
  position: fixed;
  top: 50px;
  left: 50px;
  transform: scale(4);
}
<div>
</div>
<input type="checkbox"/>

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744269177a4566014.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信