如何在滚动DIV元素时防止页面滚动?

99

我已经检查并测试了各种防止div内部滚动条滚动的函数,并结合了一个应该能够正常工作的函数。

$('.scrollable').mouseenter(function() {
    $('body').bind('mousewheel DOMMouseScroll', function() {
        return false;
    });
    $(this).bind('mousewheel DOMMouseScroll', function() {
        return true;
    });
});
$('.scrollable').mouseleave(function() {
    $('body').bind('mousewheel DOMMouseScroll', function() {
        return true;
    });
});
  • 这将停止所有滚动,而我希望在容器内仍然可以滚动
  • 鼠标离开后也不会停止

有什么想法或更好的方法吗?


你将鼠标滚轮的返回值设置为false,这可能是问题所在。我猜想你的容器在body内部,对吗? - Ricardo Binns
也许我应该澄清一下。当鼠标在元素'.scrollable'内部时,应该停用body的滚动功能。 - RIK
4
能否用纯 CSS 而不使用 JavaScript 来完成这个操作? - chharvey
有一种更好的方法使用normalScrollElements选项 https://github.com/alvarotrigo/fullPage.js#options - sherif
也许可以使用CSS的overscroll-behavior?这是一个相对较新的功能,因此旧浏览器和Safari(Mac/iOS)不支持。 - rejnok
显示剩余2条评论
14个回答

170

更新2: 我的解决方案基于完全禁用浏览器原生滚动(当光标位于DIV内部时),然后使用JavaScript手动滚动DIV(通过设置其.scrollTop属性)。另一种替代方案,我认为更好的方式是选择性地禁用浏览器的滚动以防止页面滚动,但不禁用DIV的滚动。请查看下面Rudie的答案,演示了这种解决方案。


给你:

$( '.scrollable' ).on( 'mousewheel DOMMouseScroll', function ( e ) {
    var e0 = e.originalEvent,
        delta = e0.wheelDelta || -e0.detail;

    this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
    e.preventDefault();
});

实时演示: https://jsbin.com/howojuq/edit?js,output

因此,您手动设置滚动位置,然后仅防止默认行为(即滚动DIV或整个网页)。

更新1:正如克里斯在下面的评论中指出的那样,在新版本的jQuery中,delta信息嵌套在.originalEvent对象中,即jQuery不再在其自定义事件对象中公开它,我们必须从本机事件对象中检索它。


2
@RobinKnight 不是这样的。这是工作演示:http://jsfiddle.net/XNwbt/1/,这是删除了那些行的演示:http://jsfiddle.net/XNwbt/2/。 - Šime Vidas
8
我用了这个,谢谢!不过我需要补充一下:if( e.originalEvent ) e = e.originalEvent; - Nikso
1
@NadirSampaoli,现在wheelDeltadetail的属性都嵌套在originalEvent下面。相应的第三行更改应该像这样:var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail; - user0000001
1
“30”是什么?听起来像是一个神奇的数字...它是2em吗?我的滚动条是100px,这是一个很大的差异。 - Rudie
2
@ŠimeVidas 我在将其实现到我的软件中后进行了调整。这不是关键问题,但可能需要添加一个合理性检查来防止元素没有滚动条时死锁滚动。只有当存在滚动条时,if ( $(this)[0].scrollHeight !== $(this).outerHeight() ) { //yourcode } 才会执行。 - user0000001
显示剩余30条评论

30

如果您不关心与旧版IE(< 8)的兼容性,您可以创建一个自定义的jQuery插件并在溢出元素上调用它。

这种解决方案比Šime Vidas提出的方法更有优势,因为它不会覆盖滚动行为-只在适当时阻止它。

$.fn.isolatedScroll = function() {
    this.bind('mousewheel DOMMouseScroll', function (e) {
        var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail,
            bottomOverflow = this.scrollTop + $(this).outerHeight() - this.scrollHeight >= 0,
            topOverflow = this.scrollTop <= 0;

        if ((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
            e.preventDefault();
        }
    });
    return this;
};

$('.scrollable').isolatedScroll();

2
谢谢!我认为最好不要覆盖默认行为。为了跨浏览器支持,可以使用jQuery鼠标滚轮插件。 - Meettya
不幸的是,在Chrome 52(OSX 10.10)中似乎存在故障。但在Safari中完美运行。 - frosty
1
谢谢!其他解决方案使滚动变得非常缓慢和有错误。 - agrublev
@wojcikstefan,我在Chrome中收到了这个警告:[Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' event. Consider marking event handler as 'passive' to make the page more responsive. - Syed

29

使用以下CSS属性overscroll-behavior: contain;应用于子元素。


6
相较于其他解决方案,这个本地CSS规则更为简洁实用。干得漂亮! - TechWisdom
此功能不支持Safari或iOS。 - Lee

21
我认为有时可以取消鼠标滚轮事件:http://jsfiddle.net/rudiedirkx/F8qSq/show/
$elem.on('wheel', function(e) {
    var d = e.originalEvent.deltaY,
        dir = d < 0 ? 'up' : 'down',
        stop = (dir == 'up' && this.scrollTop == 0) || 
               (dir == 'down' && this.scrollTop == this.scrollHeight-this.offsetHeight);
    stop && e.preventDefault();
});

在事件处理程序中,您需要知道:

  • 滚动方向
    d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down' 因为正数表示向下滚动
  • 滚动位置
    对于顶部使用scrollTop,对于底部使用scrollHeight - scrollTop - offsetHeight

如果您正在:

  • 向上滚动,并且top = 0,或者
  • 向下滚动,并且bottom = 0

取消事件:e.preventDefault()(甚至可以使用e.stopPropagation())。

我认为最好不要覆盖浏览器的滚动行为。只有在适用时才取消它。

它可能不是完全跨浏览器的,但应该不难。也许Mac的双重滚动方向比较麻烦...


2
如何为设备(平板电脑/手机)实现相同的功能? 我猜touchmove是绑定事件。 - usr30911

3

请看这个是否对你有帮助:

演示: jsfiddle

$('#notscroll').bind('mousewheel', function() {
     return false
});

编辑:

尝试这个:

    $("body").delegate("div.scrollable","mouseover mouseout", function(e){
       if(e.type === "mouseover"){
           $('body').bind('mousewheel',function(){
               return false;
           });
       }else if(e.type === "mouseout"){
           $('body').bind('mousewheel',function(){
               return true;
           });
       }
    });

你的元素是分开的,而我的一个包含在另一个中。 - RIK

3
在上述解决方案中,关于Firefox有一个小错误。在Firefox中,“DOMMouseScroll”事件没有e.detail属性,为了获取这个属性,你应该写下以下内容'e.originalEvent.detail'。
以下是适用于Firefox的有效解决方案:
$.fn.isolatedScroll = function() {
    this.on('mousewheel DOMMouseScroll', function (e) {
        var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.originalEvent.detail,
            bottomOverflow = (this.scrollTop + $(this).outerHeight() - this.scrollHeight) >= 0,
            topOverflow = this.scrollTop <= 0;

        if ((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
            e.preventDefault();
        }
    });
    return this;
};

这个功能运行得很好,除非你撞到div的顶部或底部,第一个事件会传递到body。在OSX上使用Chrome进行2指触摸测试。 - johnb003

3

这里提供了一个简单的解决方案,不需要使用jQuery,并且不会破坏浏览器的原生滚动(也就是说,没有人工/丑陋的滚动):

var scrollable = document.querySelector('.scrollable');

scrollable.addEventListener('wheel', function(event) {
    var deltaY = event.deltaY;
    var contentHeight = scrollable.scrollHeight;
    var visibleHeight = scrollable.offsetHeight;
    var scrollTop = scrollable.scrollTop;

    if (scrollTop === 0 && deltaY < 0)
        event.preventDefault();
    else if (visibleHeight + scrollTop === contentHeight && deltaY > 0)
        event.preventDefault();
});

在线演示:http://jsfiddle.net/ibcaliax/bwmzfmq7/4/


我刚刚发布了一个实现上述代码的NPM库: https://www.npmjs.com/package/dontscrollthebody - Iñaki Baz Castillo

3

在我看来,一种更少 hacky 的解决方案是,在鼠标悬停在可滚动的 div 上时,在 body 上设置 overflow hidden。这将防止 body 滚动,但会出现不必要的“跳跃”效果。以下解决方案解决了这个问题:

jQuery(".scrollable")
    .mouseenter(function(e) {
        // get body width now
        var body_width = jQuery("body").width();
        // set overflow hidden on body. this will prevent it scrolling
        jQuery("body").css("overflow", "hidden"); 
        // get new body width. no scrollbar now, so it will be bigger
        var new_body_width = jQuery("body").width();
        // set the difference between new width and old width as padding to prevent jumps                                     
        jQuery("body").css("padding-right", (new_body_width - body_width)+"px");
    })
    .mouseleave(function(e) {
        jQuery("body").css({
            overflow: "auto",
            padding-right: "0px"
        });
    })

如果需要,您可以让代码更加智能。例如,您可以测试身体是否已经有填充,如果是,则将新的填充添加到其中。


2

以下是我在应用程序中使用的解决方案。

我禁用了body overflow,并将整个网站html放置在容器div中。网站容器具有overflow,因此用户可以像预期的那样滚动页面。

然后我创建了一个兄弟div(#Prevent),它具有更高的z-index,覆盖整个网站。由于#Prevent具有更高的z-index,它会重叠网站容器。当#Prevent可见时,鼠标不再悬停在网站容器上,因此无法滚动。

您当然可以在标记中放置另一个div(例如您的模态框),其z-index比#Prevent更高。这使您可以创建不受滚动问题影响的弹出窗口。

此解决方案更好,因为它不会隐藏滚动条(跳跃效果)。它不需要事件侦听器,并且易于实现。它适用于所有浏览器,尽管对于IE7和8,您必须进行调整(取决于您的特定代码)。

html

<body>
  <div id="YourModal" style="display:none;"></div>
  <div id="Prevent" style="display:none;"></div>
  <div id="WebsiteContainer">
     <div id="Website">
     website goes here...
     </div>
  </div>
</body>

css

body { overflow: hidden; }

#YourModal {
 z-index:200;
 /* modal styles here */
}

#Prevent {
 z-index:100;
 position:absolute;
 left:0px;
 height:100%;
 width:100%;
 background:transparent;
}

#WebsiteContainer {
  z-index:50;
  overflow:auto;
  position: absolute;
  height:100%;
  width:100%;
}
#Website {
  position:relative;
}

jquery/js

function PreventScroll(A) { 
  switch (A) {
    case 'on': $('#Prevent').show(); break;
    case 'off': $('#Prevent').hide(); break;
  }
}

禁用/启用滚动

PreventScroll('on'); // prevent scrolling
PreventScroll('off'); // allow scrolling

2
请考虑在您的答案中包含一些信息,而不仅仅是发布代码。我们试图提供的不仅仅是“修复”,而是帮助人们学习。您应该解释原始代码中出了什么问题,您做了什么不同的事情以及为什么您的更改有效。 - Andrew Barber

1

我需要将此事件添加到可能具有滚动条的多个元素中。对于没有滚动条的情况,主滚动条无法正常工作。因此,我对@Šime的代码进行了以下小修改:

$( '.scrollable' ).on( 'mousewheel DOMMouseScroll', function ( e ) {
    if($(this).prop('scrollHeight') > $(this).height())
    {
        var e0 = e.originalEvent, delta = e0.wheelDelta || -e0.detail;

        this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
        e.preventDefault();
    }       
});

现在,只有带有滚动条的元素才能防止主滚动停止。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接