当内部元素滚动位置达到顶部/底部时防止父元素滚动?

Posted

技术标签:

【中文标题】当内部元素滚动位置达到顶部/底部时防止父元素滚动?【英文标题】:Prevent scrolling of parent element when inner element scroll position reaches top/bottom? 【发布时间】:2011-08-13 17:36:30 【问题描述】:

我有一个小“浮动工具箱”——一个带有position:fixed; overflow:auto 的 div。 效果很好。

但是当在该框内滚动(使用鼠标滚轮)并到达底部或顶部时,父元素“接管”“滚动请求”:工具框后面的文档滚动。 - 这很烦人,而不是用户“要求的”。

我正在使用 jQuery,并认为我可以使用 event.stoppropagation() 来阻止这种行为:$("#toolBox").scroll( function(event) event.stoppropagation() );

它确实进入了函数,但传播仍然发生(文档滚动) - 在 SO(和 Google)上搜索这个主题非常困难,所以我不得不问: 如何防止滚动事件的传播/冒泡?

编辑: 感谢 amustill 的工作解决方案(以及此处的鼠标滚轮插件的 Brandon Aaron:https://github.com/brandonaaron/jquery-mousewheel/raw/master/jquery.mousewheel.js

$(".ToolPage").bind('mousewheel', function(e, d)  
    var t = $(this);
    if (d > 0 && t.scrollTop() === 0) 
        e.preventDefault();
    
    else 
        if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) 
            e.preventDefault();
        
    
);

【问题讨论】:

看起来不可能。 ***.com/questions/1459676/… @Musaul - 实际上该线程提供了 2 种可能的解决方案(如果有点胭脂的话):在文档上设置 overflow:hidden,当鼠标悬停在工具箱中时,或者保存文档 scrollTop,并强制它在在 toolbox.scroll() 期间重复记录(很好)... 是的,我的意思是滚动事件冒泡。但我想它会给你解决方法。我会完全避免滚动强制选项。在滚动事件中做太多(或复杂页面中的任何事情)会使浏览器冻结一段时间,尤其是在速度较慢的计算机上。 当附加到 body 标签时,这在 IE 以外的所有东西中都能很好地工作。通过上述修复,似乎完全禁用了鼠标滚轮滚动。 请看我的回答,@Matthew。它解决了 IE 问题,以及在没有任何插件的情况下对 FireFox 进行规范化。 【参考方案1】:

为了完整起见,我添加了这个答案,因为 @amustill 接受的答案没有正确解决 Internet Explorer 中的问题。详情请参阅my original post 中的 cmets。此外,此解决方案不需要任何插件 - 只需 jQuery。

本质上,代码通过处理mousewheel 事件来工作。每个这样的事件都包含一个wheelDelta,它等于将可滚动区域移动到的px 的数量。如果这个值为&gt;0,那么我们正在滚动up。如果wheelDelta&lt;0,那么我们正在滚动down

FireFox:FireFox 使用DOMMouseScroll 作为事件,并填充originalEvent.detail,其+/- 与上述相反。它通常返回3 的间隔,而其他浏览器以120 的间隔返回滚动(至少在我的机器上)。为了更正,我们只需检测它并乘以-40 即可进行归一化。

@amustill's answer 在&lt;div&gt; 的可滚动区域已经位于顶部或底部最大位置时取消事件。但是,在delta 大于剩余可滚动空间的情况下,Internet Explorer 会忽略取消的事件。

换句话说,如果您有一个200px&lt;div&gt; 包含500px 的可滚动内容,并且当前scrollTop400,则一个mousewheel 事件告诉浏览器滚动@987654347 @ 进一步将导致 &lt;div&gt;&lt;body&gt; 滚动,因为 400 + 120 > 500

所以——要解决这个问题,我们必须做一些稍微不同的事情,如下所示:

必备的jQuery代码是:

$(document).on('DOMMouseScroll mousewheel', '.Scrollable', function(ev) 
    var $this = $(this),
        scrollTop = this.scrollTop,
        scrollHeight = this.scrollHeight,
        height = $this.innerHeight(),
        delta = (ev.type == 'DOMMouseScroll' ?
            ev.originalEvent.detail * -40 :
            ev.originalEvent.wheelDelta),
        up = delta > 0;

    var prevent = function() 
        ev.stopPropagation();
        ev.preventDefault();
        ev.returnValue = false;
        return false;
    

    if (!up && -delta > scrollHeight - height - scrollTop) 
        // Scrolling down, but this will take us past the bottom.
        $this.scrollTop(scrollHeight);
        return prevent();
     else if (up && delta > scrollTop) 
        // Scrolling up, but this will take us past the top.
        $this.scrollTop(0);
        return prevent();
    
);

本质上,此代码取消任何会创建不需要的边缘条件的滚动事件,然后使用 jQuery 将 &lt;div&gt;scrollTop 设置为最大值或最小值,具体取决于 mousewheel 的方向事件正在请求。

由于事件在任何一种情况下都被完全取消,它根本不会传播到body,因此解决了 IE 以及所有其他浏览器中的问题。

我也发了working example on jsFiddle。

【讨论】:

这是迄今为止最全面的答案。我把你的函数变成了一个 jQuery 扩展,所以它可以在 jQuery 对象链中内联使用。见this Gist。 优秀、全面且易于理解的答案。 这很好用,但是在快速滚动时似乎存在惯性问题。页面仍然滚动了大约 20 像素,这还不错。 如果内容没有溢出,不要锁定滚动:if (this.scrollHeight &lt;= parseInt($(this).css("max-height"))) return作为函数的第一行。 这对我来说非常有用,只需稍作改动。我发现如果元素的底部有填充/边距,将元素的高度设置为 $this.height() 并不能正确响应元素的底部。所以我把它改成了 $this.outerHeight(true)。【参考方案2】:

此线程中给出的所有解决方案都没有提到现有的 - 和本地 - 解决此问题的方法,无需重新排序 DOM 和/或使用事件预防技巧。但有一个很好的理由:这种方式是专有的 - 并且仅在 MS Web 平台上可用。引用MSDN:

-ms-scroll-chaining 属性 - 指定当用户在操作期间达到滚动限制时发生的滚动行为。属性值:

链式 - 初始值。当用户在操作期间达到滚动限制时,最近的可滚动父元素开始滚动。不显示反弹效果。

- 当用户在操作过程中达到滚动限制时会显示弹跳效果。

当然,此属性仅在 IE10+/Edge 上受支持。不过,这里有一个telling quote

为了让您了解防止滚动链接的流行程度, 根据我的快速 http-archive 搜索“-ms-scroll-chaining: none” 尽管受到限制,但在前 300K 页面的 0.4% 中使用 功能,仅在 IE/Edge 上受支持。

现在好消息,大家!从 Chrome 63 开始,我们终于也有了针对基于 Blink 的平台的原生解决方案——这就是 Chrome(显然)和 android WebView(很快)。

引用the introducing article:

overscroll-behavior 属性是一项新的 CSS 功能,用于控制 过度滚动容器时发生的行为 (包括页面本身)。您可以使用它来取消滚动链接, 禁用/自定义下拉刷新操作,禁用橡皮筋 对 ios 的影响(当 Safari 实现过度滚动行为时)等等。[...]

该属性采用三个可能的值:

自动 - 默认。源自元素的滚动可能会传播到 祖先元素。

contain - 防止滚动链接。卷轴不 传播到祖先,但显示节点内的局部效果。 例如,Android 上的过度滚动发光效果或 iOS 上的橡皮筋效果,当用户点击时通知用户 滚动边界。注意:使用 overscroll-behavior: 包含在 html 元素防止过度滚动导航操作。

none - 与 contains 相同,但它还可以防止节点本身内的过度滚动效果(例如 Android 过度滚动发光或 iOS 橡皮筋)。

[...] 最好的部分是使用 overscroll-behavior 不会产生不利影响 像介绍中提到的 hack 一样影响页面性能!

这是feature in action。这里是对应的CSS Module document

更新: Firefox 从 59 版开始加入俱乐部,MS Edge 有望在 18 版中实现此功能。这里是对应的caniusage

【讨论】:

caniuse.com/#search=overscroll-behavior 估计以后会越来越好 Safari 不支持overscroll-behavior 这种策略似乎对本身没有足够内容可滚动的内部元素无效。 自 Safari 14.1 起,该功能位于标志后面,可以在“实验功能”开发人员菜单中启用。所以很可能,他们会在未来的某个时候启用这个功能......【参考方案3】:

可以使用 Brandon Aaron 的 Mousewheel plugin。

这是一个演示:http://jsbin.com/jivutakama/edit?html,js,output

$(function() 

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

  toolbox.bind('mousewheel', function(e, d) 
    if((this.scrollTop === (scrollHeight - height) && d < 0) || (this.scrollTop === 0 && d > 0)) 
      e.preventDefault();
    
  );

);

【讨论】:

如果您有滚动惯性并滚动子容器然后将鼠标移出子容器,也会有些奇怪。惯性会附着在父容器或任何其他容器上。我在 OSX Lion 上的 chrome 魔幻鼠标上体验了这一点 jsbin 示例不会阻止 iPad 上的页面滚动。 @Heraldmonkey 我知道这个问题。我希望有时间修改所有浏览器和设备的答案。 jsbin 示例在 osx 触控板上不适合我 在 Mac OS X 和 Chrome 32 上不适合我。【参考方案4】:

我知道这是一个很老的问题,但由于这是谷歌的***结果之一......我不得不在没有 jQuery 的情况下以某种方式取消滚动冒泡,这段代码对我有用:

function preventDefault(e) 
  e = e || window.event;
  if (e.preventDefault)
    e.preventDefault();
  e.returnValue = false;  


document.getElementById('a').onmousewheel = function(e)  
  document.getElementById('a').scrollTop -= e. wheelDeltaY; 
  preventDefault(e);

【讨论】:

有趣的想法,..鼠标滚轮事件不是跨浏览器的,滚动距离需要标准化,见***.com/a/5542105/126600 我们可以有嵌套的视图,这使得它有问题。 这适用于移动触控吗?谢谢 @AGamePlayer 绝对不会,因为触摸设备不会触发 mousewheel 事件,除非你真的在它们上面使用鼠标。否则他们会进行正常的滚动。不过,您可能很幸运(ab)使用触摸事件......【参考方案5】:

编辑:CodePen example

对于 AngularJS,我定义了以下指令:

module.directive('isolateScrolling', function () 
  return 
    restrict: 'A',
      link: function (scope, element, attr) 
        element.bind('DOMMouseScroll', function (e) 
          if (e.detail > 0 && this.clientHeight + this.scrollTop == this.scrollHeight) 
            this.scrollTop = this.scrollHeight - this.clientHeight;
            e.stopPropagation();
            e.preventDefault();
            return false;
          
          else if (e.detail < 0 && this.scrollTop <= 0) 
            this.scrollTop = 0;
            e.stopPropagation();
            e.preventDefault();
            return false;
          
        );
        element.bind('mousewheel', function (e) 
          if (e.deltaY > 0 && this.clientHeight + this.scrollTop >= this.scrollHeight) 
            this.scrollTop = this.scrollHeight - this.clientHeight;
            e.stopPropagation();
            e.preventDefault();
            return false;
          
          else if (e.deltaY < 0 && this.scrollTop <= 0) 
            this.scrollTop = 0;
            e.stopPropagation();
            e.preventDefault();
            return false;
          

          return true;
        );
      
  ;
);

然后将其添加到可滚动元素(下拉菜单中):

<div class="dropdown">
  <button type="button" class="btn dropdown-toggle">Rename <span class="caret"></span></button>
  <ul class="dropdown-menu" isolate-scrolling>
    <li ng-repeat="s in savedSettings | objectToArray | orderBy:'name' track by s.name">
      <a ng-click="renameSettings(s.name)">s.name</a>
    </li>
  </ul>
</div>

在 Chrome 和 Firefox 上测试。当在滚动区域的顶部或底部附近(但不是在)附近进行较大的鼠标滚轮移动时,Chrome 的平滑滚动可以解决此问题。

【讨论】:

这在 Chrome 34 中似乎不起作用。绑定正在触发,但当 &lt;ul&gt; 到达底部时页面继续滚动。 它在 Chrome 36 中为我工作。 在 Firefox 或 IE 上不起作用,添加 DOMMouseScroll 后也不起作用。 它对我有用,但值是倒置的 ;) 只需将 > 0 更改为 工作,我使用的唯一区别是更改 e.deltaY 为 e.originalEvent.deltaY 不知道为什么......为什么你使用不同的绑定而不是单个函数?为我工作...在 FF chrome safari 上测试【参考方案6】:

有很多这样的问题,有很多答案,但我找不到不涉及事件、脚本、插件等的令人满意的解决方案。我想直接用 HTML 和 CSS 来解决。我终于找到了一个可行的解决方案,尽管它涉及重组标记以中断事件链。


1.基本问题

应用于模态元素的滚动输入(即:鼠标滚轮)将溢出到祖先元素并沿相同方向滚动,如果某些此类元素是可滚动的:

(所有示例均应在桌面分辨率下查看)

https://jsfiddle.net/ybkbg26c/5/

HTML:

<div id="parent">
  <div id="modal">
    This text is pretty long here.  Hope fully, we will get some scroll bars.
  </div>
</div>

CSS:

#modal 
  position: absolute;
  height: 100px;
  width: 100px;
  top: 20%;
  left: 20%;
  overflow-y: scroll;

#parent 
  height: 4000px;


2。模态滚动上没有父滚动

祖先最终滚动的原因是滚动事件冒泡并且链上的某些元素能够处理它。阻止这种情况的一种方法是确保链上的所有元素都不知道如何处理滚动。在我们的示例中,我们可以重构树以将模式移出父元素。出于晦涩的原因,仅保留父级和模态 DOM 同级是不够的;父元素必须由另一个建立新的堆叠上下文的元素包裹。围绕父级的绝对定位的包装器可以解决问题。

我们得到的结果是,只要modal接收到滚动事件,事件就不会冒泡到“父”元素。

通常应该可以重新设计 DOM 树以支持此行为,而不会影响最终用户所看到的内容。

https://jsfiddle.net/0bqq31Lv/3/

HTML:

<div id="context">
  <div id="parent">
  </div>
</div>
<div id="modal">
  This text is pretty long here.  Hope fully, we will get some scroll bars.
</div>

CSS(仅新):

#context 
  position: absolute;
  overflow-y: scroll;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;


3.启动时除了模态外,任何地方都没有滚动

上面的解决方案仍然允许父级接收滚动事件,只要它们不被模态窗口拦截(即,如果在光标不在模态窗口上时由鼠标滚轮触发)。这有时是不可取的,我们可能希望在模式启动时禁止所有背景滚动。为此,我们需要在模态框后面插入一个跨越整个视口的额外堆叠上下文。我们可以通过显示一个绝对定位的叠加层来做到这一点,必要时它可以是完全透明的(但不是visibility:hidden)。

https://jsfiddle.net/0bqq31Lv/2/

HTML:

<div id="context">
  <div id="parent">
  </div>
</div>
<div id="overlay">  
</div>
<div id="modal">
  This text is pretty long here.  Hope fully, we will get some scroll bars.
</div>

CSS(在 #2 之上新增):

#overlay 
  background-color: transparent;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;

【讨论】:

当“模态”是另一个可滚动元素的子元素时,就会出现基本问题 - 脚本似乎不可避免。在我的原始代码中,工具箱只是其中之一 - 每个都在单独的选项卡中(可能被移动到 - 但那会很大) @T4NK3R 是的,有一些权衡。在我的案例中,重构 DOM 比将 UI javascript 扔到问题上要好得多。总的来说,我认为对于从头开始设计树的人来说,这是一个值得考虑的选择。 这是唯一能有效避免问题的方法,而不是试图用大量 JS 解决问题,而这从一开始就可以避免。 我已经阅读了很多关于这个问题的答案,其中很多都获得了 100 多分。这是唯一真正有意义的。【参考方案7】:

这是一个纯 JavaScript 版本:

function scroll(e) 
  var delta = (e.type === "mousewheel") ? e.wheelDelta : e.detail * -40;
  if (delta < 0 && (this.scrollHeight - this.offsetHeight - this.scrollTop) <= 0) 
    this.scrollTop = this.scrollHeight;
    e.preventDefault();
   else if (delta > 0 && delta > this.scrollTop) 
    this.scrollTop = 0;
    e.preventDefault();
  

document.querySelectorAll(".scroller").addEventListener("mousewheel", scroll);
document.querySelectorAll(".scroller").addEventListener("DOMMouseScroll", scroll);

【讨论】:

不错的解决方案。在 chrome 中效果很好 我遇到了 Firefox 问题,这个解决方案有效。【参考方案8】:

作为变体,为了避免scrollmousewheel 处理的性能问题,您可以使用如下代码:

css:

body.noscroll 
    overflow: hidden;

.scrollable 
    max-height: 200px;
    overflow-y: scroll;
    border: 1px solid #ccc;

html:

<div class="scrollable">
...A bunch of items to make the div scroll...
</div>
...A bunch of text to make the body scroll...

js:

var $document = $(document),
    $body = $('body'),
    $scrolable = $('.scrollable');

$scrolable.on(
          'mouseenter': function () 
            // add hack class to prevent workspace scroll when scroll outside
            $body.addClass('noscroll');
          ,
          'mouseleave': function () 
            // remove hack class to allow scroll
            $body.removeClass('noscroll');
          
        );

工作示例:http://jsbin.com/damuwinarata/4

【讨论】:

在某些浏览器中隐藏时的滚动将所有页面向左移动,因为滚动消失...【参考方案9】:

Angular JS 指令

我不得不包装一个角度指令。以下是此处其他答案的混搭。在 Chrome 和 Internet Explorer 11 上测试。

var app = angular.module('myApp');

app.directive("preventParentScroll", function () 
    return 
        restrict: "A",
        scope: false,
        link: function (scope, elm, attr) 
            elm.bind('mousewheel', onMouseWheel);
            function onMouseWheel(e) 
                elm[0].scrollTop -= (e.wheelDeltaY || (e.originalEvent && (e.originalEvent.wheelDeltaY || e.originalEvent.wheelDelta)) || e.wheelDelta || 0);
                e.stopPropagation();
                e.preventDefault();
                e.returnValue = false;
            
        
    
);

用法

<div prevent-parent-scroll>
    ...
</div>

希望这对通过 Google 搜索到达这里的下一个人有所帮助。

【讨论】:

我真的很喜欢这个解决方案,它运行良好。我只需要在 angular 1.5 中更改一件事。由于不需要 e.originalEvet,只需直接从 e 读取车轮增量。 (对于wheelDeltaY 和wheelDelta) @Tom 感谢您的反馈。我认为e.originalEvent 是为了支持 IE 嗯,有趣,听起来像 IE。当我在 win 机器附近时,我会检查它。【参考方案10】:

将原生元素滚动属性与鼠标滚轮插件中的增量值结合使用:

$elem.on('mousewheel', function (e, delta) 
    // Restricts mouse scrolling to the scrolling range of this element.
    if (
        this.scrollTop < 1 && delta > 0 ||
        (this.clientHeight + this.scrollTop) === this.scrollHeight && delta < 0
    ) 
        e.preventDefault();
    
);

【讨论】:

【参考方案11】:

如果有人仍在为此寻找解决方案,以下插件可以完成工作http://mohammadyounes.github.io/jquery-scrollLock/

它完全解决了在给定容器内锁定鼠标滚轮滚动的问题,防止它传播到父元素。

不改变滚轮滚动速度,不影响用户体验。并且无论操作系统鼠标滚轮垂直滚动速度如何,您都会得到相同的行为(在 Windows 上,它可以设置为一个屏幕或一行,每个槽口最多 100 行)。

演示:http://mohammadyounes.github.io/jquery-scrollLock/example/

来源:https://github.com/MohammadYounes/jquery-scrollLock

【讨论】:

听起来不错,但是当我在 MacOS 上的 Firefox 或 Chrome 中尝试您的演示时,我仍然看到此问题中描述的那种有问题的滚动行为。 @Ghazgkull 检查系统偏好设置中的常规部分,如果“显示滚动条”设置为“滚动时”,那么您需要强制它,因为库将无法检测到滚动条的存在(参见github.com/MohammadYounes/jquery-scrollLock/pull/3)。【参考方案12】:

amustill 作为淘汰处理程序的回答:

ko.bindingHandlers.preventParentScroll = 
    init: function (element, valueAccessor, allBindingsAccessor, context) 
        $(element).mousewheel(function (e, d) 
            var t = $(this);
            if (d > 0 && t.scrollTop() === 0) 
                e.preventDefault();
            
            else 
                if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) 
                    e.preventDefault();
                
            
        );
    
;

【讨论】:

我也会在这里添加对 ko.utils.domNodeDisposal.addDisposeCallback 的调用。我知道这可能没有必要,因为 KO 和 jQuery 之间的节点清理工作得很好,但是覆盖你的基础很好:)【参考方案13】:

上面的方法不是很自然,经过一些谷歌搜索后我找到了一个更好的解决方案,并且不需要 jQuery。请参阅 [1] 和演示 [2]。

  var element = document.getElementById('uf-notice-ul');

  var isMacWebkit = (navigator.userAgent.indexOf("Macintosh") !== -1 &&
    navigator.userAgent.indexOf("WebKit") !== -1);
  var isFirefox = (navigator.userAgent.indexOf("firefox") !== -1);

  element.onwheel = wheelHandler; // Future browsers
  element.onmousewheel = wheelHandler; // Most current browsers
  if (isFirefox) 
    element.scrollTop = 0;
    element.addEventListener("DOMMouseScroll", wheelHandler, false);
  
  // prevent from scrolling parrent elements
  function wheelHandler(event) 
    var e = event || window.event; // Standard or IE event object

    // Extract the amount of rotation from the event object, looking
    // for properties of a wheel event object, a mousewheel event object 
    // (in both its 2D and 1D forms), and the Firefox DOMMouseScroll event.
    // Scale the deltas so that one "click" toward the screen is 30 pixels.
    // If future browsers fire both "wheel" and "mousewheel" for the same
    // event, we'll end up double-counting it here. Hopefully, however,
    // cancelling the wheel event will prevent generation of mousewheel.
    var deltaX = e.deltaX * -30 || // wheel event
      e.wheelDeltaX / 4 || // mousewheel
      0; // property not defined
    var deltaY = e.deltaY * -30 || // wheel event
      e.wheelDeltaY / 4 || // mousewheel event in Webkit
      (e.wheelDeltaY === undefined && // if there is no 2D property then 
        e.wheelDelta / 4) || // use the 1D wheel property
      e.detail * -10 || // Firefox DOMMouseScroll event
      0; // property not defined

    // Most browsers generate one event with delta 120 per mousewheel click.
    // On Macs, however, the mousewheels seem to be velocity-sensitive and
    // the delta values are often larger multiples of 120, at 
    // least with the Apple Mouse. Use browser-testing to defeat this.
    if (isMacWebkit) 
      deltaX /= 30;
      deltaY /= 30;
    
    e.currentTarget.scrollTop -= deltaY;
    // If we ever get a mousewheel or wheel event in (a future version of)
    // Firefox, then we don't need DOMMouseScroll anymore.
    if (isFirefox && e.type !== "DOMMouseScroll") 
      element.removeEventListener("DOMMouseScroll", wheelHandler, false);
    
    // Don't let this event bubble. Prevent any default action.
    // This stops the browser from using the mousewheel event to scroll
    // the document. Hopefully calling preventDefault() on a wheel event
    // will also prevent the generation of a mousewheel event for the
    // same rotation.
    if (e.preventDefault) e.preventDefault();
    if (e.stopPropagation) e.stopPropagation();
    e.cancelBubble = true; // IE events
    e.returnValue = false; // IE events
    return false;
  

[1]https://dimakuzmich.wordpress.com/2013/07/16/prevent-scrolling-of-parent-element-with-javascript/

[2]http://jsfiddle.net/dima_k/5mPkB/1/

【讨论】:

非常好的解决方案 - 这个阻止了父级的滚动,即使小容器不需要滚动(它的内容适合里面) - 尽管有些人会吠叫 - 也许会做这个功能可选的第二类...? Ups,说得太早了。在我的 W10 上的 Chrome 中,向下滚动时(在您的 jsfiddle 上)滚动会直接跳到底部 - 这不好【参考方案14】:

这实际上在 AngularJS 中有效。 在 Chrome 和 Firefox 上测试。

.directive('stopScroll', function () 
    return 
        restrict: 'A',
        link: function (scope, element, attr) 
            element.bind('mousewheel', function (e) 
                var $this = $(this),
                    scrollTop = this.scrollTop,
                    scrollHeight = this.scrollHeight,
                    height = $this.height(),
                    delta = (e.type == 'DOMMouseScroll' ?
                    e.originalEvent.detail * -40 :
                        e.originalEvent.wheelDelta),
                    up = delta > 0;

                var prevent = function() 
                    e.stopPropagation();
                    e.preventDefault();
                    e.returnValue = false;
                    return false;
                ;

                if (!up && -delta > scrollHeight - height - scrollTop) 
                    // Scrolling down, but this will take us past the bottom.
                    $this.scrollTop(scrollHeight);
                    return prevent();
                 else if (up && delta > scrollTop) 
                    // Scrolling up, but this will take us past the top.
                    $this.scrollTop(0);
                    return prevent();
                
            );
        
    ;
)

【讨论】:

【参考方案15】:

你可以用 CSS 实现这个结果,即

.isolate-scrolling 
    overscroll-behavior: contain;

这只会在您的鼠标将子元素留给父元素时滚动父容器。

【讨论】:

【参考方案16】:

我的 jQuery 插件:

$('.child').dontScrollParent();

$.fn.dontScrollParent = function()

    this.bind('mousewheel DOMMouseScroll',function(e)
    
        var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;

        if (delta > 0 && $(this).scrollTop() <= 0)
            return false;
        if (delta < 0 && $(this).scrollTop() >= this.scrollHeight - $(this).height())
            return false;

        return true;
    );

【讨论】:

这在 Firefox 中似乎不起作用。【参考方案17】:

我有类似的情况,我是这样解决的: 我所有的可滚动元素都获得了 scrollable 类。

$(document).on('wheel', '.scrollable', function(evt) 
  var offsetTop = this.scrollTop + parseInt(evt.originalEvent.deltaY, 10);
  var offsetBottom = this.scrollHeight - this.getBoundingClientRect().height - offsetTop;

  if (offsetTop < 0 || offsetBottom < 0) 
    evt.preventDefault();
   else 
    evt.stopImmediatePropagation();
  
);

stopImmediatePropagation() 确保不要从可滚动的子区域滚动父可滚动区域。

这是一个普通的 JS 实现: http://jsbin.com/lugim/2/edit?js,output

【讨论】:

你能澄清一下“offsetTop 考虑使用中间变量为其命名。 ...或者重命名已有的,或者添加一些cmets来解释。 如果我理解正确,您正在检查元素是否滚动到底部或顶部边界,如果事件应该运行的话。【参考方案18】:

这里有新的网络开发者。这在 IE 和 Chrome 上对我来说都是一种魅力。

static preventScrollPropagation(e: HTMLElement) 
    e.onmousewheel = (ev) => 
        var preventScroll = false;
        var isScrollingDown = ev.wheelDelta < 0;
        if (isScrollingDown) 
            var isAtBottom = e.scrollTop + e.clientHeight == e.scrollHeight;
            if (isAtBottom) 
                preventScroll = true;
            
         else 
            var isAtTop = e.scrollTop == 0;
            if (isAtTop) 
                preventScroll = true;
            
        
        if (preventScroll) 
            ev.preventDefault();
        
    

不要让行数欺骗了你,这很简单 - 只是为了可读性而有点冗长(自我记录代码 ftw 对吗?)

另外我要提一下,这里的语言是TypeScript,但和往常一样,将其转换为JS很简单。

【讨论】:

【参考方案19】:

对于那些使用 MooTools 的人,这里是等效的代码:

            'mousewheel': function(event)
            var height = this.getSize().y;
            height -= 2;    // Not sure why I need this bodge
            if ((this.scrollTop === (this.scrollHeight - height) && event.wheel < 0) || 
                (this.scrollTop === 0 && event.wheel > 0)) 
                event.preventDefault();
            

请记住,我和其他人一样,必须将值调整几个 px,这就是 height -= 2 的用途。

基本上主要区别在于,在 MooTools 中,增量信息来自 event.wheel,而不是传递给事件的额外参数。

另外,如果我将此代码绑定到任何东西,我会遇到问题(绑定函数的 event.target.scrollHeight 不等于非绑定函数的 this.scrollHeight)

希望这对某人有帮助,就像这篇文章对我有帮助一样;)

【讨论】:

虽然我对你的代码一无所知,但我敢打赌你必须从高度中减去 2,因为getSize() 不考虑边界(在你的情况下,两者都是 1px顶部和底部)。【参考方案20】:

查看Leland Kwong's 代码。

基本思路是将wheeling事件绑定到子元素上,然后使用子元素的原生javascript属性scrollHeight和jquery属性outerHeight来检测滚动的结束,然后return false到wheeling 事件以防止任何滚动。

var scrollableDist,curScrollPos,wheelEvent,dY;
$('#child-element').on('wheel', function(e)
  scrollableDist = $(this)[0].scrollHeight - $(this).outerHeight();
  curScrollPos = $(this).scrollTop();
  wheelEvent = e.originalEvent;
  dY = wheelEvent.deltaY;
  if ((dY>0 && curScrollPos >= scrollableDist) ||
      (dY<0 && curScrollPos <= 0)) 
    return false;
  
);

【讨论】:

很好,保持“本地” - 但是如果 deltaY 是“矮胖的” - 最后 20-30px 不会保持隐藏吗?【参考方案21】:

我从所选库中提取了这个:https://github.com/harvesthq/chosen/blob/master/coffee/chosen.jquery.coffee

function preventParentScroll(evt) 
    var delta = evt.deltaY || -evt.wheelDelta || (evt && evt.detail)
    if (delta) 
        evt.preventDefault()
        if (evt.type ==  'DOMMouseScroll') 
            delta = delta * 40  
        
        fakeTable.scrollTop = delta + fakeTable.scrollTop
    

var el = document.getElementById('some-id')
el.addEventListener('mousewheel', preventParentScroll)
el.addEventListener('DOMMouseScroll', preventParentScroll)

这对我有用。

【讨论】:

【参考方案22】:

具有模拟 Internet Explorer 自然滚动功能的 jQuery 插件

  $.fn.mousewheelStopPropagation = function(options) 
    options = $.extend(
        // defaults
        wheelstop: null // Function
        , options);

    // Compatibilities
    var isMsIE = ('Microsoft Internet Explorer' === navigator.appName);
    var docElt = document.documentElement,
        mousewheelEventName = 'mousewheel';
    if('onmousewheel' in docElt) 
        mousewheelEventName = 'mousewheel';
     else if('onwheel' in docElt) 
        mousewheelEventName = 'wheel';
     else if('DOMMouseScroll' in docElt) 
        mousewheelEventName = 'DOMMouseScroll';
    
    if(!mousewheelEventName)  return this; 

    function mousewheelPrevent(event) 
        event.preventDefault();
        event.stopPropagation();
        if('function' === typeof options.wheelstop) 
            options.wheelstop(event);
        
    

    return this.each(function() 
        var _this = this,
            $this = $(_this);
        $this.on(mousewheelEventName, function(event) 
            var origiEvent = event.originalEvent;
            var scrollTop = _this.scrollTop,
                scrollMax = _this.scrollHeight - $this.outerHeight(),
                delta = -origiEvent.wheelDelta;
            if(isNaN(delta)) 
                delta = origiEvent.deltaY;
            
            var scrollUp = delta < 0;
            if((scrollUp && scrollTop <= 0) || (!scrollUp && scrollTop >= scrollMax)) 
                mousewheelPrevent(event);
             else if(isMsIE) 
                // Fix Internet Explorer and emulate natural scrolling
                var animOpt =  duration:200, easing:'linear' ;
                if(scrollUp && -delta > scrollTop) 
                    $this.stop(true).animate( scrollTop:0 , animOpt);
                    mousewheelPrevent(event);
                 else if(!scrollUp && delta > scrollMax - scrollTop) 
                    $this.stop(true).animate( scrollTop:scrollMax , animOpt);
                    mousewheelPrevent(event);
                
            
        );
    );
;

https://github.com/basselin/jquery-mousewheel-stop-propagation/blob/master/mousewheelStopPropagation.js

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。 Tiben 干得好。谢谢你,真的。 +1 努力【参考方案23】:

我能找到的最佳解决方案是监听窗口上的滚动事件,如果子 div 可见,则将 scrollTop 设置为前一个 scrollTop。

prevScrollPos = 0
$(window).scroll (ev) ->
    if $('#mydiv').is(':visible')
        document.body.scrollTop = prevScrollPos
    else
        prevScrollPos = document.body.scrollTop

如果您触发大量滚动事件,子 div 的背景会闪烁,因此可以对此进行调整,但几乎不会注意到它,这对于我的用例来说已经足够了。

【讨论】:

【参考方案24】:

不要在body 上使用overflow: hidden;。它会自动将所有内容滚动到顶部。也不需要 JavaScript。使用overflow: auto;:

HTML 结构

<div class="overlay">
    <div class="overlay-content"></div>
</div>

<div class="background-content">
    lengthy content here
</div>

造型

.overlay
    position: fixed;
    top: 0px;
    left: 0px;
    right: 0px;
    bottom: 0px;
    background-color: rgba(0, 0, 0, 0.8);

    .overlay-content 
        height: 100%;
        overflow: scroll;
    


.background-content
    height: 100%;
    overflow: auto;

试玩演示here。

【讨论】:

我无法让它工作 - 你的操场上有一百万行 CSS 和 JS 我建议在body 上使用overflow: hidden;,只要它不会自动滚动到页面顶部。但是当它自动滚动到顶部时,您应该尝试在body 上使用overflow: auto; height: 100%【参考方案25】:

当鼠标悬停在可滚动元素上时,还有一个有趣的技巧可以锁定父级的scrollTop。这样您就不必实现自己的滚轮滚动。

这是一个防止文档滚动的示例,但它可以针对任何元素进行调整。

scrollable.mouseenter(function ()

  var scroll = $(document).scrollTop();
  $(document).on('scroll.trap', function ()
  
    if ($(document).scrollTop() != scroll) $(document).scrollTop(scroll);
  );
);

scrollable.mouseleave(function ()

  $(document).off('scroll.trap');
);

【讨论】:

【参考方案26】:

M.K. 在他的回答中提供了一个很棒的插件。插件可以找到here。但是,为了完整起见,我认为将它放在一个 AngularJS 的答案中是个好主意。

    从注入 bower 或 npm(首选)开始

    bower install jquery-scrollLock --save
    npm install jquery-scroll-lock --save
    

    添加以下指令。我选择将其添加为属性

    (function() 
       'use strict';
    
        angular
           .module('app')
           .directive('isolateScrolling', isolateScrolling);
    
           function isolateScrolling() 
               return 
                   restrict: 'A',
                   link: function(sc, elem, attrs) 
                      $('.scroll-container').scrollLock();
                   
               
           
    )();
    

    插件未能在其网站中记录的重要部分是它必须遵循的 HTML 结构。

    <div class="scroll-container locked">
        <div class="scrollable" isolate-scrolling>
             ... whatever ...
        </div>
    </div>
    

属性isolate-scrolling 必须包含scrollable 类,并且它都需要在scroll-container 类或您选择的任何类中,并且locked 类必须是级联的。

【讨论】:

【参考方案27】:

值得一提的是,在处理固定位置元素时,使用 reactJS、AngularJS、VueJS 等现代框架可以轻松解决这个问题。例如侧面板或叠加元素。

该技术称为“门户”,这意味着应用程序中使用的组件之一,无需从您使用它的位置实际提取它,将其子组件安装在 body 元素的底部,在您试图避免滚动的父级之外。

请注意,它不会避免滚动正文元素本身。您可以结合此技术并将您的应用安装在滚动 div 中以达到预期的效果。

React 的 material-ui 中的示例 Portal 实现:https://material-ui-next.com/api/portal/

【讨论】:

【参考方案28】:

有 ES 6 crossbrowser + mobile vanila js 决定:

function stopParentScroll(selector) 
    let last_touch;
    let MouseWheelHandler = (e, selector) => 
        let delta;
        if(e.deltaY)
            delta = e.deltaY;
        else if(e.wheelDelta)
            delta = e.wheelDelta;
        else if(e.changedTouches)
            if(!last_touch)
                last_touch = e.changedTouches[0].clientY;
            
            else
                if(e.changedTouches[0].clientY > last_touch)
                    delta = -1;
                
                else
                    delta = 1;
                
            
        
        let prevent = function() 
            e.stopPropagation();
            e.preventDefault();
            e.returnValue = false;
            return false;
        ;

        if(selector.scrollTop === 0 && delta < 0)
            return prevent();
        
        else if(selector.scrollTop === (selector.scrollHeight - selector.clientHeight) && delta > 0)
            return prevent();
        
    ;

    selector.onwheel = e => MouseWheelHandler(e, selector); 
    selector.onmousewheel = e => MouseWheelHandler(e, selector); 
    selector.ontouchmove  = e => MouseWheelHandler(e, selector);

【讨论】:

【参考方案29】:

我正在为 MooTools 搜索这个,这是第一个出现的。 最初的 MooTools 示例可以向上滚动,但不能向下滚动,所以我决定写这个。

MooTools 1.4.5:http://jsfiddle.net/3MzFJ/ MooTools 1.3.2:http://jsfiddle.net/VhnD4/ MooTools 1.2.6:http://jsfiddle.net/xWrw4/
var stopScroll = function (e) 
    var scrollTo = null;
    if (e.event.type === 'mousewheel') 
        scrollTo = (e.event.wheelDelta * -1);
     else if (e.event.type === 'DOMMouseScroll') 
        scrollTo = 40 * e.event.detail;
    
    if (scrollTo) 
        e.preventDefault();
        this.scrollTo(0, scrollTo + this.scrollTop);
    
    return false;
;

用法:

(function)($)
    window.addEvent('domready', function()
        $$('.scrollable').addEvents(
             'mousewheel': stopScroll,
             'DOMMouseScroll': stopScroll
        );
    );
)(document.id);

【讨论】:

【参考方案30】:

mouseweel 事件的简单解决方案:

$('.element').bind('mousewheel', function(e, d) 
    console.log(this.scrollTop,this.scrollHeight,this.offsetHeight,d);
    if((this.scrollTop === (this.scrollHeight - this.offsetHeight) && d < 0)
        || (this.scrollTop === 0 && d > 0)) 
        e.preventDefault();
    
);

【讨论】:

以上是关于当内部元素滚动位置达到顶部/底部时防止父元素滚动?的主要内容,如果未能解决你的问题,请参考以下文章

滚动时如何保持 <li> 元素固定在 <ul> 元素的顶部和底部

scrollTop总是为0

如何在页面滚动时隐藏元素?

当点击视口底部时,防止 Mobile Safari 显示工具栏

有啥办法可以防止 FormBuilder 元素滚动到页面顶部?

位置:粘性 - 与javascript高度调整结合使用时滚动弹跳