13

My goal is to have an element which allows:

  1. elements underneath to be clicked on/interacted with,
  2. scrolling

The solution to 1 is widely known to be pointer-events: none. This is as described in Click through a DIV to underlying elements.

However, the element can not be scrolled, because the scroll bar appears on the element with pointer-events: none. This can be seen in this example: http://jsbin.com/madure/2/edit?html,css,output.

Is there a workaround to this, or is it something that would need to be addressed at the browser level? Perhaps with an additional rule, pointer-events: scrollOnly.

Community
  • 1
  • 1
Oliver Joseph Ash
  • 3,138
  • 2
  • 27
  • 47
  • 1
    Are you ok with it not blocking mouse events over/leave, and CSS states like hover? If so, I can provide a CSS-only solution that will block all click-type mouse/pointer events on the parent. Let me know and I will post the solution. – csuwldcat Jan 11 '17 at 19:11
  • @csuwldcat I would like all pointer events other than scroll/mousewheel (or a click on the scrollbar itself) for my use case, so hover is needed. However it may be worth posting anyway as it could help others. :-) – Oliver Joseph Ash Jan 13 '17 at 10:30
  • Hello Oliver, I am facing the exact same issue as you are. I need to scroll on a div, but let every other events "pass through" to the divs behind it. Have you solved your problem? Thanks – JkSuf Mar 30 '17 at 11:57

2 Answers2

3

The pointer-events CSS property specifies under what circumstances (if any) a particular graphic element can become the target of mouse events.

So basically if you make the upper layer insensitive for click it will be so for wheel events too. I suggest on of two things

JavaScript workaround: Which basically use the fact that:

Note that preventing an element from being the target of mouse events by using pointer-events does not necessarily mean that mouse event listeners on that element cannot or will not be triggered

 $(function(){
  $("#stage-layer").on("wheel",function(e){
   eo=e.originalEvent;
   s=$("#scrollable")
   switch(eo.deltaMode){
    case 0:   //DOM_DELTA_PIXEL  Chrome
     s.scrollTop(eo.deltaY+s.scrollTop())
     s.scrollLeft(eo.deltaX+s.scrollLeft()) 
     break;
    case 1:   //DOM_DELTA_LINE  Firefox
     s.scrollTop(15*eo.deltaY+s.scrollTop()) //scroll(e.deltaX, e.deltaY);  Just a random small amount of scrolling 
     s.scrollLeft(15*eo.deltaX+s.scrollLeft())
     break;
    case 2:   //DOM_DELTA_PAGE
     s.scrollTop(0.03*eo.deltaY+s.scrollTop())
     s.scrollLeft(0.03*eo.deltaX+s.scrollLeft())
     break;
   }
   e.stopPropagation();
   e.preventDefault()
  })
  
 })
.container {
    position: relative;
    width: 400px;
    height: 400px;
    border: 2px solid black;
}

#stage-layer {
    position: absolute;
    width: 100%;
    box-sizing: border-box;
    height: 100%;
    border: 2px solid yellow;
}

#application-layer {
    position: relative;
    box-sizing: border-box;
    height: 100%;
    border: 2px solid pink;
    pointer-events: none;
}

rect:hover {
    fill: blue;
}

#scrollable {
    overflow: auto;
    color: hotpink;
    height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

    <div class="container">
        <svg id="stage-layer">
            <rect width="200" height="200"></rect>
        </svg>
        <div id="application-layer">
            <div id="scrollable">
                <p>foo1</p>
                <p>foo2</p>
                <p>foo3</p>
                <p>foo4</p>
                <p>foo5</p>
                <p>foo6</p>
            </div>
        </div>
    </div>

A nice tip:

This will not probably yield an immediate solution but it is a good choice for long term web development :

We would like to provide finer grained control (than just auto and none) in HTML ... if you have any particular things that you would like to be able to do with this property, then please add them to the Use Cases section of this wiki page (don't worry about keeping it tidy).

Source : pointer-events

user10089632
  • 5,216
  • 1
  • 26
  • 34
3

Translated @user10089632's answer to vanilla javascript

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;
.container {
    position: relative;
    width: 400px;
    height: 400px;
    border: 2px solid black;
}

#stage-layer {
    position: absolute;
    width: 100%;
    box-sizing: border-box;
    height: 100%;
    border: 2px solid yellow;
}

#application-layer {
    position: relative;
    box-sizing: border-box;
    height: 100%;
    border: 2px solid pink;
    pointer-events: none;
}

rect:hover {
    fill: blue;
}

#scrollable {
    overflow: auto;
    color: hotpink;
    height: 100px;
}
<div class="container">
    <svg id="stage-layer">
        <rect width="200" height="200"></rect>
    </svg>
    <div id="application-layer">
        <div id="scrollable">
            <p>foo1</p>
            <p>foo2</p>
            <p>foo3</p>
            <p>foo4</p>
            <p>foo5</p>
            <p>foo6</p>
        </div>
    </div>
</div>
Ben
  • 757
  • 1
  • 7
  • 15