skip to Main Content

I need to scroll one specific element on the page even when the mouse isn’t hovering over that element…

I would normally put the div that needs scrolling on the top layer with a transparent background but I have a navigation menu that needs to be seen/interacted with on the same page.

In my experience CSS is extremely finicky; one change that is supposed to be aesthetic only ends up altering how the elements interact with each other and it is frustrating. I am hours into this project and I really don’t feel like starting from scratch again just for one bug like this… and having to test if the website works after every CSS addition.

I’ll put a jsfiddle of what my website is pretty much structured like.

html {
  overflow: hidden;
}

.fixed {
  position: fixed;
  background-color: blue;
  width: 50%;
}

.scrollable {
  background-color: red;
  height: 100vh;
  padding-left: 50%;
  overflow-y: scroll;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
  <div class="fixed">
    <h1>
    Lorem ipsum dolor sit amet
    </h1>
    
  </div>
  <div class="scrollable">
    <ul>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
       <li>_________</li>
     </ul>
   </div>
</body>
</html>

I can scroll anywhere on the red div and it works, but if I scroll on the blue div the red div won’t scroll. Changing the blue div to pointer-events: none; makes it all work (this was the only answer I could find online) except for the fact that I have a navigation bar with pointer events inside the blue div. You would think after being around for centuries HTML would find a simple way to let a fixed element have pointer events AND scroll events!

TL;DR Is there a way to make a scroll anywhere on the screen scroll one specific div? Thanks in advance.

2

Answers


  1. Chosen as BEST ANSWER

    Building off of 0stone0's answer (with some added stuff to make the scroll more smooth and testing all the pointer events).

    // scroll bar functionality for fixed element
    // https://stackoverflow.com/questions/41592349/allow-pointer-click-events-to-pass-through-element-whilst-maintaining-scroll-f#
    function scroller(event) {
      scrollable = document.getElementById("scrollable");
      switch (event.deltaMode) {
        case 0: //DOM_DELTA_PIXEL       Chrome
          scrollable.scrollTop += event.deltaY
          scrollable.scrollLeft += event.deltaX
          break;
        case 1: //DOM_DELTA_LINE        Firefox
          scrollable.scrollTop += 15 * event.deltaY
          scrollable.scrollLeft += 15 * event.deltaX
          break;
        case 2: //DOM_DELTA_PAGE
          scrollable.scrollTop += 0.03 * event.deltaY
          scrollable.scrollLeft += 0.03 * event.deltaX
          break;
      }
      event.stopPropagation();
      event.preventDefault()
    }
    
    document.onwheel = scroller;
    
    
    // scroll to an item that inside a div
    //   https://stackoverflow.com/questions/45408920/plain-javascript-scrollintoview-inside-div
    function scrollParentToChild(parent, child, threshold = 0) {
      // Where is the parent on page
      const parentRect = parent.getBoundingClientRect();
      // What can you see?
      const parentViewableArea = {
        height: parent.clientHeight,
        width: parent.clientWidth,
      };
      // Where is the child
      const childRect = child.getBoundingClientRect();
      // Is the child viewable?
      const isViewableVertically = childRect.top >= parentRect.top &&
        childRect.bottom <= parentRect.top + parentViewableArea.height;
      const isViewableHorizontally = childRect.left >= parentRect.left &&
        childRect.right <= parentRect.left + parentViewableArea.width;
      // if you can't see the child try to scroll parent
      if (!isViewableVertically || !isViewableHorizontally) {
        // Should we scroll using top or bottom? Find the smaller ABS adjustment
        const scrollTop = childRect.top - parentRect.top;
        const scrollBot = childRect.bottom - parentRect.bottom;
        const scrollLeft = childRect.left - parentRect.left;
        const scrollRight = childRect.right - parentRect.right;
        if (Math.abs(scrollTop) < Math.abs(scrollBot) && Math.abs(scrollLeft) < Math.abs(scrollRight)) {
          // we're nearer to the top and left of the list
          parent.scrollTo({
            top: parent.scrollTop + scrollTop - threshold,
            left: parent.scrollLeft + scrollLeft - threshold,
            behavior: 'smooth',
          });
        } else {
          // we're nearer to the bottom and right of the list
          parent.scrollTo({
            top: parent.scrollTop + scrollBot + threshold,
            left: parent.scrollLeft + scrollRight + threshold,
            behavior: 'smooth',
          });
        }
      }
    }
    html,
    body {
      overflow: hidden;
      margin: 0;
      padding: 0;
      background-color: pink;
    }
    
    .container {
      position: relative;
      width: 100%;
      height: 100vh;
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    #stage-layer {
      position: absolute;
      width: 100vw;
      height: 100vh;
      background-color: yellow;
      margin: 0;
      padding: 0;
    }
    
    #application-layer {
      position: relative;
      height: 100%;
      margin: 0;
      padding: 0;
    }
    
    rect:hover {
      fill: blue;
    }
    
    #scrollable {
      position: relative;
      overflow: auto;
      color: hotpink;
      height: 100%;
      width: 100%;
      background-color: blue;
      padding-left: 0;
    }
    
    p:hover {
      color: white;
      transform: translateX(20px);
      transition: 0.3s;
    }
    
    .menu {
      position: fixed;
      background-color: red;
      width: 100px;
      z-index: 100;
    }
    
    .hover:hover {
      background-color: orange;
    }
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="styles.css">
      <title>Blank HTML</title>
    </head>
    
    <body>
      <div class="container">
        <svg id="stage-layer">
                <rect></rect>
            </svg>
        <div id="application-layer">
          <div class="menu">
            <p onClick="scrollParentToChild(document.getElementById('scrollable'), document.getElementById('first'));">
              go to top of page</p>
            <p onClick="scrollParentToChild(document.getElementById('scrollable'), document.getElementById('last'));">
              go to bottom of page</p>
          </div>
    
          <div id="scrollable">
            <li id="first">_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li class="hover">HOVER TEST</li>
            <li>_________</li>
            <li>_________</li>
            <li><a class="link" href="https://www.google.com/">LINK TEST</a></li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li>_________</li>
            <li id="last">_________</li>
          </div>
    
        </div>
      </div>
      <script src="/website/test/scripts.js"></script>
    </body>
    
    </html>


  2. You could use the wheel event to capture ‘scroll’s on nonscrollable elements, and then translate that to the fixed scrollTop

    const s = document.querySelector(".scrollable");
    const f = document.querySelector(".fixed");
    
    f.addEventListener('wheel', (e) => (s.scrollTop += (e.deltaY < 1) ? 30 : -30));
    html {
      overflow: hidden;
    }
    
    .fixed {
      position: fixed;
      background-color: blue;
      width: 50%;
    }
    
    .scrollable {
      background-color: red;
      height: 50vh;
      padding-left: 50%;
      overflow-y: scroll;
    }
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
      <div class="fixed">
        <h1>
        Lorem ipsum dolor sit amet
        </h1>
        
      </div>
      <div class="scrollable">
        <ul>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
           <li>_________</li>
         </ul>
       </div>
    </body>
    </html>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search