skip to Main Content

I have a sidebar for mobile and it takes up 300px from the left of the screen when it shows. I check if it shows with a variable called isOpen.

<Navbar isOpen={isOpen} setIsOpen={setIsOpen} />
<div className={isOpen ? "content behind" : "content"}>
//content
</div>

How do I disable clicking and scrolling for the div under the navbar when the sidebar is active?

.content.behind{
   pointer-events: none;
}

I tried using this but scrolling is still possible.

.content.behind{
   overflow: hidden;
}

I tried this too, but when the sidebar is shown, the top of the content section is shown because the overflows are hidden.

I also found this custom hook on github. useScrollBlock It worked, but when I use it, scrollable is disabled for the whole body. It should be possible to scroll for the sidebar.

2

Answers


  1. I’m assuming .contant is your scrollable element (rather then the whole body). In that case, using both pointer-events: none and overflow: hidden seems to work just fine in the example below.

    You can scroll down, open the sidebar, and the list’s scroll position doesn’t change. However, you’ll notice the list moves horizontally when the scrollbar is hidden (at least on Windows, where the scrollbar is not an overlay like in macOS/iOS):

    const sidebar = document.getElementById('sidebar')
    const content = document.getElementById('content')
    const toggleButton = document.getElementById('toggleButton')
    
    toggleButton.addEventListener('click', () => {
      sidebar.classList.toggle('open')
      content.classList.toggle('behind')
      toggleButton.textContent = toggleButton.textContent === '👉' ? '👈' : '👉'
    })
    body {
      margin: 0;
    }
    
    #sidebar {
      position: fixed;
      top: 0;
      left: 0;
      width: 300px;
      height: 100vh;  
      background: black;
      transform: translate(-300px, 0);
      transition: transform linear 300ms;
      z-index: 1;
    }
    
    #sidebar.open {;
      transform: translate(0, 0);
    }
    
    #content {
      max-height: 100vh;
      overflow-y: scroll;
      padding: 8px;
      box-sizing: border-box;
    }
    
    #content.behind {
      opacity: 0.5;
      pointer-events: none;
      overflow: hidden;
    }
    
    #content > p {
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100px;
      margin: 0;
      background: cyan;
      font-family: monospace;
      font-size: 32px;
    }
    
    #content > p + p {
      margin: 8px 0 0;
    }
    
    
    #content > p:hover {
      background: yellow;
    }
    
    #toggleButton {
      position: fixed;
      top: 8px;
      left: 8px;
      width: 40px;
      height: 40px;  
      background: white;
      border: 3px solid black;
      z-index: 2;
    }
    <div id="sidebar">
    </div>
    
    <div id="content">
      <p>0</p>
      <p>1</p>
      <p>2</p>
      <p>3</p>
      <p>4</p>
      <p>5</p>
      <p>6</p>
      <p>7</p>
      <p>8</p>
      <p>9</p>
    </div>
    
    <button id="toggleButton">👉</button>

    In any case, maybe your problem is device-specific, or it depends on the overall HTML structure you have. You could instead just add an overlay over #content, which will prevent users from scrolling it or interacting with its content:

    const sidebar = document.getElementById('sidebar')
    const overlay = document.getElementById('overlay')
    const toggleButton = document.getElementById('toggleButton')
    
    toggleButton.addEventListener('click', () => {
      sidebar.classList.toggle('open')
      overlay.classList.toggle('visible')
      toggleButton.textContent = toggleButton.textContent === '👉' ? '👈' : '👉'
    })
    body {
      margin: 0;
    }
    
    #sidebar {
      position: fixed;
      top: 0;
      left: 0;
      width: 300px;
      height: 100vh;  
      background: black;
      transform: translate(-300px, 0);
      transition: transform linear 300ms;
      z-index: 1;
    }
    
    #sidebar.open {;
      transform: translate(0, 0);
    }
    
    #overlay {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: white;
      opacity: 0;
      transition: opacity linear 300ms;
      pointer-events: none;
    }
    
    #overlay.visible {
      opacity: 0.5;
      pointer-events: auto;
    }
    
    #content {
      max-height: 100vh;
      overflow-y: scroll;
      padding: 8px;
      box-sizing: border-box;
    }
    
    #content > p {
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100px;
      margin: 0;
      background: cyan;
      font-family: monospace;
      font-size: 32px;
    }
    
    #content > p + p {
      margin: 8px 0 0;
    }
    
    
    #content > p:hover {
      background: yellow;
    }
    
    #toggleButton {
      position: fixed;
      top: 8px;
      left: 8px;
      width: 40px;
      height: 40px;  
      background: white;
      border: 3px solid black;
      z-index: 2;
    }
    <div id="sidebar">
    </div>
    
    <div id="overlay">
    </div>
    
    <div id="content">
      <p>0</p>
      <p>1</p>
      <p>2</p>
      <p>3</p>
      <p>4</p>
      <p>5</p>
      <p>6</p>
      <p>7</p>
      <p>8</p>
      <p>9</p>
    </div>
    
    <button id="toggleButton">👉</button>
    Login or Signup to reply.
  2. In order to prevent any interaction with the main content of the page what is usually done is creating a div that is placed on top of the content (but below the modal/sidebar):

    {isOpen ? <div className="modaloverlay"/> : ''}
    

    Then in the CSS define it with a z-index that is greater than everything in the main content but lower than the one used in the modal (my modal has z-index: 1000):

    .modaloverlay{
      position:fixed;
      top:0;left:0;width:100vw;height:100vh;
      background-color:black;
      opacity:0.5;
      z-index: 999;
    }
    

    And for hiding the scrollbars, it depends on the rest of your page design (which you haven’t shared), but the hiding of the overflow is usually needed at the body element. It can be done via CSS class, but instead I did it via useEffect:

    useEffect(() => {
      if (isOpen) {
        document.body.style.overflow = 'hidden';
      }
      return () => {
        document.body.style.overflow = null;
      };
    }, [isOpen]);
    

    You can a working example in the snippet below:

    const Navbar = ({ isOpen, setIsOpen }) => {
      const buttonText = isOpen ? "Close" : "Open";
      const onClick = () => setIsOpen(!isOpen);
      return (
        <div className="navbar">
          {isOpen
            ? (<div>
                <p>Text on <b>OPENED</b> NavBar</p>
                <p>Extra text on NavBar...</p>
                <p>More extra text on NavBar!</p>
              </div>)
            : (<div>NavBar is Closed</div>)}
          <div><button onClick={onClick}>{buttonText}</button></div>
        </div>
      );
    };
    
    const Content = () => {
      const t = (n) => (
        <div key={n}>
          <h1>Title {n}</h1>
          <p>Paragraph {n}.1</p>
          <p>Paragraph {n}.2</p>
        </div>)
      return (
        <div>
          {[t(1),t(2),t(3),t(4),t(5),t(6),t(7),t(8)]}
        </div>
      );
    };
    
    const App = () => {
      const [isOpen, setIsOpen] = React.useState(false);
      
      React.useEffect(() => {
        if (isOpen) {
          document.body.style.overflow = 'hidden';
        }
        return () => {
          document.body.style.overflow = null;
        };
      }, [isOpen]);
    
      return (
        <div className="app">
          <Navbar isOpen={isOpen} setIsOpen={setIsOpen} />
          <div className="content">
            <Content />
            {isOpen ? <div className="modaloverlay"/> : ''}
          </div>
        </div>
      );
    };
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);
    .app {
      width: 400px;
      margin-top: 50px;
    }
    .navbar {
      position: fixed;
      top: 0px;
      width: 100vw;
      width: 400px;
      background-color: yellow;
      text-align: center;
      z-index: 1000;
    }
    .content {
      background-color: green;
    }
    .modaloverlay{
      position:fixed;
      top:0;left:0;width:100vw;height:100vh;
      background-color:black;
      opacity:0.5;
      z-index: 999;
    }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    
    <div id="root"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search