13

I have a page that scrolls normal (vertically) and I'd like to have a div that scrolls horizontally on mousewheel down, then resumes vertical scrolling once the horizontal scrolling is done. Here's what I'm trying to accomplish.

  • Page scrolls normal (vertically)
  • Once I reach a the div #scroll, I'd like the page scrolling to stop and I'd like the content inside #scroll to scroll vertically
  • Once I scroll to the end of #scroll, I'd like the normal page scroll (vertically) to resume.

I have tried a few solutions but run in to the following problems

  • When I horizontal the #scroll content the vertical page scroll doesn't stop
  • When I stop the vertical scroll and get to the end of the horizontal scroll, I have to scroll like 50 times just to get out of that div.

Here's what I have so far...

var scroller = {};
scroller.e = document.getElementById("scroll");

if (scroller.e.addEventListener) {
    scroller.e.addEventListener("mousewheel", MouseWheelHandler, false);
    scroller.e.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
} else scroller.e.attachEvent("onmousewheel", MouseWheelHandler);

function MouseWheelHandler(e) {

    // cross-browser wheel delta
    var e = window.event || e;
    var delta = - 30 * (Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))));

    var pst = $('#scroll').scrollLeft() + delta;

    if (pst < 0) {
        pst = 0;
    } else if (pst > $('.box-wrap').width()) {
        pst = $('.box-wrap').width();
    }

    $('#scroll').scrollLeft(pst);

    return false;
}


var toolbox = $('#scroll'),
    height = toolbox.height(),
    scrollHeight = toolbox.get(0).scrollHeight;

toolbox.off("mousewheel").on("mousewheel", function (event) {
  var blockScrolling = this.scrollTop === scrollHeight - height && event.deltaY < 0 || this.scrollTop === 0 && event.deltaY > 0;
  return !blockScrolling;
});
#wrap {
  max-width: 600px;
  margin: 0 auto;
}

#scroll {
    width: 600px;
    border: 1px solid #111;
    padding: 0px;
    margin: 0px;
    overflow-x: scroll;
    overflow-y: hidden;
}

.box-wrap{
    padding: 0px;
    margin: 0px;
    height: 200px;
    width: 2040px;
}

.box {
    height: 200px;
    width: 200px;
    padding: 0px;
    background: #123;
    display: inline-block;
    color: #fff;
    font-size: 20px;
    text-align: center;
    line-height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="wrap">

  <h1>asl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdk</h1>

  <div id="scroll">
    <div class="box-wrap">
      <div class="box">1</div>
      <div class="box">2</div>
      <div class="box">3</div>
      <div class="box">4</div>
      <div class="box">5</div>
      <div class="box">6</div>
      <div class="box">7</div>
      <div class="box">8</div>
      <div class="box">9</div>
      <div class="box">10</div>     
    </div>
  </div>

  <h1>asl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdk</h1>

</div>

The problem with this solution is that the vertical page scroll doesn't stop when I get to the horizontal scroll section.

Does anyone have any suggestions on how to accomplish what I want? If I'm not explaining this right please let me know.

Thanks!

colxi
  • 7,640
  • 2
  • 45
  • 43
Axion
  • 682
  • 4
  • 20
  • This is probably part of what you're trying to achieve: https://stackoverflow.com/questions/3656592/how-to-programmatically-disable-page-scrolling-with-jquery – moronator Mar 13 '19 at 23:55

5 Answers5

10

You were almost there. Here's a blocker for your scroll:

$("#scroll").off("mousewheel").on("mousewheel", function(ev) {
  var el = $(ev.currentTarget);
  return ev.originalEvent.deltaY > 0
    ? el[0].scrollWidth - el.scrollLeft() <= el.innerWidth()
    : el.scrollLeft() === 0;
});

Updated pen.
To watch them, use this logger in the mousewheel event:

console.log(
  'ev.originalEvent.deltaY:', ev.originalEvent.deltaY,
  '\nel[0].scrollWidth:',el[0].scrollWidth, 
  '\nel.scrollLeft():', el.scrollLeft(), 
  '\nel.innerWidth()',el.innerWidth()
);
tao
  • 82,996
  • 16
  • 114
  • 150
  • Thanks for the suggestion but I tried that and the vertical page scroll didn't stop when scrolling inside the #scroll div. – Axion Mar 14 '19 at 00:41
  • It works, as long as you scroll inside `#scroll` (because that's where you bound your `mousewheel`). If you want to always scroll the element when it's into view, you need to bind on window scroll, return false when the element has scrolled into view, scroll the element until the end and then start returning true again. However, users won't be able to get out of the scroll-lock when the element is into view even if they scroll outside of the element, which is a huge UX problem. You'll make a lot of users close your website in frustration and never return. – tao Mar 14 '19 at 01:09
  • This works great in Chrome! Any idea how to get it working with Firefox? – Axion Mar 26 '19 at 16:42
7

You need to use e.preventDefault() to avoid the screen to scroll vertically. However, it only needs to be applied when when the horizontal container allows more scrolling, to the left or to the right according to the scroll direction.

This will obviously only work when the scroll is performed over the horizontally scrollable element.

Implementation example (scroll down to test):

let scrollSpeed = 30;
let scroller = document.getElementById("scroll");

scroller.addEventListener("mousewheel", e=>{
  // block if e.deltaY==0
  if( !e.deltaY ) return;
  // Set scrollDirection (-1 = up // 1 = down)
  let scrollDirection = (e.deltaY > 0) ? 1 : -1;
  // convert vertical scroll into horizontal
  scroller.scrollLeft += scrollSpeed * scrollDirection;
  let scrollLeft = Math.round(scroller.scrollLeft);
  // calculate box total vertical scroll 
  let maxScrollLeft = Math.round( scroller.scrollWidth - scroller.clientWidth );
  // if element scroll has not finished scrolling
  // prevent window to scroll
  if( 
    (scrollDirection === -1  && scrollLeft > 0) ||
    (scrollDirection === 1 && scrollLeft < maxScrollLeft ) 
  ) e.preventDefault()
  // done!
  return true;
}, false);
#wrap {
  max-width: 600px;
  margin : auto;
}

.dummy-content{
  height: 400px;
  background: red
}

#scroll {
    overflow-x: scroll;
    overflow-y: hidden;
    margin :20px 0;
}

#scroll>div:first-child{
    height: 200px;
    width: 2040px;
    background-image: linear-gradient(to right ,red, yellow);
}
<div id="wrap">
<div class='dummy-content'></div>
  <div id="scroll">
      <div>
          <!-- Your horizontal content -->
      </div>
  </div>
  <div class='dummy-content'></div>
</div>
colxi
  • 7,640
  • 2
  • 45
  • 43
  • Hmm, I tried that and the horizontal section still moves when mousewheeling down. – Axion Mar 14 '19 at 00:39
  • i updated my code, try again (it works for me in Chrome) – colxi Mar 14 '19 at 00:44
  • Thanks! I tested and it works great in Chome... but I can't get it to work in Firefox. Any idea why it doesn't work in FF? – Axion Mar 15 '19 at 18:08
  • @Axion Use the 'wheel' event instead of 'mousewheel'. Note that all the examples add real bugs to any application that implements this. For example, Chrome will start scrolling both the body element vertically AND the element with the wheel event. Firefox has a similar problem but sometimes fails to latch onto the element scroll. Only an "always preventDefault()" solution works to prevent odd scrolling behaviors, but that would fail to answer the question. A better solution might be a new CSS scrolling rule, but that requires standards bodies and browser vendors to get involved. – CubicleSoft Apr 19 '20 at 02:17
5

https://codepen.io/anon/pen/oOgrEY

As Andrei Gheorghiu answer explain. It's just lacking:

e.preventDefault(); // << add this
e.stopPropagation(); // << add this

before return false;

var scroller = {};
scroller.e = document.getElementById("scroll");

if (scroller.e.addEventListener) {
  scroller.e.addEventListener("mousewheel", MouseWheelHandler, false);
  scroller.e.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
} else scroller.e.attachEvent("onmousewheel", MouseWheelHandler);

function MouseWheelHandler(e) {
  // cross-browser wheel delta
  var e = window.event || e;
  var delta = -30 * Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));

  var pst = $("#scroll").scrollLeft() + delta;

  if (pst < 0) {
    pst = 0;
  } else if (pst > $(".box-wrap").width()) {
    pst = $(".box-wrap").width();
  }

  $("#scroll").scrollLeft(pst);
  e.preventDefault(); // << add this
  e.stopPropagation(); // << add this
  return false;
}

$("#scroll").off("mousewheel").on("mousewheel", function(ev) {
  var el = $(ev.currentTarget);
  return ev.originalEvent.deltaY > 0
    ? el[0].scrollWidth - el.scrollLeft() <= el.innerWidth()
    : el.scrollLeft() === 0;
});
#wrap {
  max-width: 600px;
  margin: 0 auto;
}

#scroll {
    width: 600px;
    border: 1px solid #111;
    padding: 0px;
    margin: 0px;
    overflow-x: scroll;
    overflow-y: hidden;
}

.box-wrap{
    padding: 0px;
    margin: 0px;
    height: 200px;
    width: 2040px;
}

.box {
    height: 200px;
    width: 200px;
    padding: 0px;
    background: #123;
    display: inline-block;
    color: #fff;
    font-size: 20px;
    text-align: center;
    line-height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="wrap">

  <h1>asl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdk</h1>

  <div id="scroll">
    <div class="box-wrap">
      <div class="box">1</div>
      <div class="box">2</div>
      <div class="box">3</div>
      <div class="box">4</div>
      <div class="box">5</div>
      <div class="box">6</div>
      <div class="box">7</div>
      <div class="box">8</div>
      <div class="box">9</div>
      <div class="box">10</div>     
    </div>
  </div>

  <h1>asl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdk</h1>
  
</div>

Tested with: Version 73.0.3683.86 (Official Build) (64-bit), Firefox 66.0.2 (64-bit), Microsoft Edge 42.17134.1.0(Microsoft EdgeHTML 17.17134), Internet Explorer 11.648.17134.0

Mukyuu
  • 6,436
  • 8
  • 40
  • 59
1

For making the #scroll div stick to the page, don't stop the normal vertical scrolling. Instead, increase the top position of the div so that it looks like the #scroll div is staying in the same position.

I wrapped the portion that needs to stick inside a #fixedSec.

Also, to make the .box-wrap div scroll with the vertical window scroll, use the scrollTop() function, and then translate your .box-wrap accordingly.

The codepen link is this.

Below is code.

var scroller = {};
var topSpace = $('#topHeading').height();
const scrollWidth = $('.box-wrap').width();
console.log($(window).width());

scroller.e = document.getElementById("scroll");
$(window).scroll(function(event) {
  var scroll = $(window).scrollTop();

  if (scroll > 785 && ((765 - scroll) > ($(window).width() - $(".box-wrap").width()))) {


    var scrollPos = scroll - 770;
    $('#fixedSec').css({
      'top': scrollPos + "px"
    })
    $('.box-wrap').css('transform', 'translateX(' + (755 - scroll) + 'px)');



  }


});
h1 {
  width: 500px;
}

#wrap {
  margin: 0 auto;
}

#fixedSec {
  position: relative;
}

#scroll {
  width: 100vw;
  border: 1px solid #111;
  padding: 0px;
  margin: 0px;
  overflow-x: scroll;
  overflow-y: hidden;
}

.box-wrap {
  padding: 0px;
  margin: 0px;
  height: 200px;
  width: 2040px;
}

.box {
  height: 200px;
  width: 200px;
  padding: 0px;
  background: #123;
  display: inline-block;
  color: #fff;
  font-size: 20px;
  text-align: center;
  line-height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="wrap">

  <h1 id="topHeading">asl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas
    fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh
    sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas
    kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdk</h1>

  <div id="fixedSec">
    <div id="scroll">
      <div class="box-wrap">
        <div class="box">1</div>
        <div class="box">2</div>
        <div class="box">3</div>
        <div class="box">4</div>
        <div class="box">5</div>
        <div class="box">6</div>
        <div class="box">7</div>
        <div class="box">8</div>
        <div class="box">9</div>
        <div class="box">10</div>
      </div>

    </div>

    <h1>asl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas
      fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh
      sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs
      kjfhas kljfh sakjfhas fsdkasl fjas fhljksa flkjsahf kjsah fkjlsa fkjshf kljsha fkljhs kjfhas kljfh sakjfhas fsdk</h1>
  </div>


</div>

Note

Even if you make the overflow of #scroll hidden, the solution will still work since its using the transform: translateX() function to horizontally scroll the .box-wrap. Personally, I'll make the overflow:hidden as the scroll bar looks ugly.

Hope this implementation works for you.

TeslaLDead
  • 93
  • 7
0

You need to stop the default event behavior (vertcal scrolling) with e.preventDefault() in your mouseWheelHandler.

Rachael Dawn
  • 819
  • 6
  • 13
Olafant
  • 829
  • 1
  • 7
  • 16