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

IT技术 javascript jquery scroll event-bubbling event-propagation
2021-01-11 18:03:30

我有一个“浮动工具箱”——一个带有position:fixed; overflow:auto. 工作得很好。

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

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

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

编辑:
工作解决方案感谢 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();
        }
    }
});
6个回答

我添加这个答案是为了完整性,因为@amustill 接受的答案没有正确解决 Internet Explorer 中的问题有关详细信息,请参阅我原帖中的评论此外,这个解决方案不需要任何插件——只需要 jQuery。

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

FireFox:FireFoxDOMMouseScroll用作事件并填充originalEvent.detail,其+/-与上述内容相反。它通常返回 的间隔3,而其他浏览器以 的间隔返回滚动120(至少在我的机器上)。为了纠正,我们简单地检测它并乘以-40归一化。

如果<div>的可滚动区域已经位于顶部或底部的最大位置,@amustill 的回答通过取消事件来起作用但是,大于剩余可滚动空间的情况下Internet Explorer会忽略已取消的事件delta

换句话说,如果您有一个包含可滚动内容200pxtall并且当前is 则告诉浏览器进一步滚动事件将导致 the滚动,因为+ > <div>500pxscrollTop400mousewheel120px<div><body>400120500

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

必要的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设置scrollTop<div>到最大值或最小值,这取决于哪个方向mousewheel事件请求。

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

我还在jsFiddle 上提供了一个工作示例

优秀,全面且易于理解的答案。
2021-03-12 18:03:30
如果内容没有溢出,不要锁定滚动:if (this.scrollHeight <= parseInt($(this).css("max-height"))) return作为函数的第一行。
2021-04-03 18:03:30
到目前为止,这是最全面的答案。我把你的函数变成了一个 jQuery 扩展,所以它可以在 jQuery 对象链中内联使用。看到这个要点
2021-04-05 18:03:30
这很有效,但在快速滚动时似乎存在惯性问题。页面仍然滚动了大约20个像素,这还不错。
2021-04-07 18:03:30
这对我很有用,只需稍作改动。我发现将元素的高度设置为 $this.height() 不能正确响应元素的底部,如果它有填充/边距。所以我把它改成了 $this.outerHeight(true)。
2021-04-08 18:03:30

使用 Brandon Aaron 的Mousewheel 插件是可能的

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

jsbin 示例在 osx 触控板上对我不起作用
2021-03-14 18:03:30
@Heraldmonkey 我知道这个问题。我希望有时间为所有浏览器和设备修改答案。
2021-03-16 18:03:30
jsbin 示例不会阻止在 iPad 上滚动页面。
2021-03-26 18:03:30
如果您有滚动惯性并滚动子容器,然后将鼠标移出子容器,那么还有一些奇怪之处。惯性会影响父容器或任何其他容器。我在 OSX Lion 上的 chrome 魔法鼠标上体验过这个
2021-03-30 18:03:30
在 Mac OS X 和 Chrome 32 上对我不起作用。
2021-04-08 18:03:30

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

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

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

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

当然,此属性仅在 IE10+/Edge 上受支持。不过,这里有一个有说服力的报价

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

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

引用介绍文章

反弹时,行为属性是一个新的CSS功能,可以控制的,当你反弹时的容器(包括网页本身)会发生什么情况的行为。您可以使用它来取消滚动链接、禁用/自定义下拉刷新操作、禁用 iOS 上的橡皮筋效果(当 Safari 实现过度滚动行为时)等等。[...]

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

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

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

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

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

这是此功能的实际应用这是相应的CSS Module 文档

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

Safari 不支持overscroll-behavior.
2021-03-30 18:03:30
caniuse.com/#search=overscroll-behavior我想它会在未来变得更好
2021-04-01 18:03:30
对于本身没有足够内容可滚动的内部元素,此策略似乎无效。
2021-04-06 18:03:30

我知道这是一个很老的问题,但由于这是谷歌的顶级结果之一......我不得不以某种方式取消没有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);
}
我们可以有嵌套的视图,这使得它有问题。
2021-03-12 18:03:30
有趣的想法,..mousewheel 事件不是跨浏览器,滚动距离需要标准化,请参阅stackoverflow.com/a/5542105/126600
2021-03-28 18:03:30
这适用于移动触控吗?谢谢
2021-04-06 18:03:30
@AGamePlayer 绝对不会,因为触摸设备不会触发鼠标滚轮事件,除非您实际上在它们上使用鼠标。否则他们会进行正常的滚动。虽然你可能很幸运(ab)使用触摸事件......
2021-04-10 18:03:30

编辑:CodePen 示例

对于 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;
        });
      }
  };
});

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

<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 的平滑滚动会阻止这种攻击。

但是在 Firefox 或 IE 上不起作用,添加DOMMouseScroll.
2021-03-17 18:03:30
它在 Chrome 36 中对我有用。
2021-03-23 18:03:30
它对我有用,但值是反向​​的;) 只需将 > 0 更改为 < 0,反之亦然。在 Windows 8.1 上测试,使用 chrome、firefox 和 ie11
2021-04-03 18:03:30
这在 Chrome 34 中似乎不起作用。绑定正在触发,但页面在<ul>到达底部时继续滚动
2021-04-07 18:03:30
工作,我使用的唯一区别是将 e.deltaY 更改为 e.originalEvent.deltaY 不知道为什么......为什么你使用不同的绑定而不是单个函数?为我工作......在FF chrome safari上测试
2021-04-07 18:03:30