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

T4N*_*K3R 190 javascript jquery scroll event-bubbling event-propagation

我有一个小"浮动工具箱" - 一个div与position:fixed; overflow:auto.工作得很好.

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

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

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

编辑:
工作解决方案感谢amustill(以及Brandon Aaron的mousewheel-plugin: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();
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

Tro*_*ord 168

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

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

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

@ amustill的答案通过在<div>可滚动区域已经位于顶部或底部最大位置时取消该事件来起作用.但是,Internet Explorerdelta大于剩余可滚动空间的情况下忽略已取消的事件.

换句话说,如果你有一个包含可滚动内容的200px高,并且当前是,则告诉浏览器进一步滚动的事件将导致滚动和滚动,因为+ > .<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();
    }
});
Run Code Online (Sandbox Code Playgroud)

本质上,该代码可以取消这将创建不希望的边缘条件的任何滚动事件,则用了jQuery设置scrollTop所述的<div>到最大值或最小值,这取决于哪个方向上的mousewheel事件请求.

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

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

  • 到目前为止,这是最全面的答案.我将你的函数变成了jQuery扩展,因此它可以在jQuery对象链中内联使用.见[this Gist](https://gist.github.com/theftprevention/5959411). (13认同)
  • 如果内容没有溢出,请不要锁定滚动:`if(this.scrollHeight <= parseInt($(this).css("max-height")))返回`作为函数的第一行. (5认同)
  • 优秀,全面且易于理解的答案. (2认同)
  • 这非常有效,但滚动速度非常快时似乎存在惯性问题.该页面仍然滚动了大约20个像素,这不是太糟糕. (2认同)
  • 这对我来说非常有用,只需稍加改动.我发现将元素的高度设置为$ this.height()如果有填充/边距则没有正确响应元素的底部.所以我把它改成了$ this.outerHeight(true). (2认同)

amu*_*ill 42

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

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

  • 在Mac OS X和Chrome 32上不适用于我. (20认同)
  • jsbin示例不会阻止iPad上的页面滚动. (5认同)
  • 如果您有滚动惯性并滚动子容器然后将鼠标移出孩子,那么也会有一些奇怪之处.惯性龋齿到父母或任何其他容器上.我在OSX Lion上使用Chrome中的魔术鼠标体验了这一点 (2认同)
  • @Heraldmonkey我知道这个问题.我希望有时间修改所有浏览器和设备的答案. (2认同)
  • 在osx触控板上,jsbin示例对我不起作用 (2认同)

Neb*_*ril 22

我知道这是一个相当古老的问题,但由于这是google中的最佳结果之一...我不得不以某种方式取消滚动冒泡而不使用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);
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣的想法,..鼠标滚轮事件不是跨浏览器,滚动距离需要规范化,请参阅http://stackoverflow.com/a/5542105/126600 (2认同)

dOx*_*xxx 19

编辑: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;
        });
      }
  };
});
Run Code Online (Sandbox Code Playgroud)

然后将其添加到可滚动元素(下拉菜单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>
Run Code Online (Sandbox Code Playgroud)

在Chrome和Firefox上测试过.当在滚动区域的顶部或底部附近(但不是在)处进行大型鼠标滚轮移动时,Chrome的平滑滚动会破坏此黑客攻击.


rai*_*7ow 18

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

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

链式 - 初始值.当用户在操纵期间点击滚动限制时,最近的可滚动父元素开始滚动.没有显示弹跳效果.

none - 当用户在操作期间达到滚动限制时,会显示跳出效果.

当然,IE10 +/Edge仅支持此属性.不过,这是一个有说服力的引用:

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

现在好消息,大家好!从Chrome 63开始,我们终于可以为基于Blink的平台提供本机解决方案 - 这既是Chrome(显然)也是Android WebView(很快).

引用介绍文章:

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

该属性有三个可能的值:

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

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

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

[...] 最好的部分是使用过度滚动行为不会像介绍中提到的黑客那样对页面性能产生负面影响!

以下是此功能的实际应用.这是相应的CSS模块文档.

更新:自59版以来,Firefox已加入俱乐部,预计MS Edge将在版本18中实现此功能.这是相应的caniusage.

  • https://caniuse.com/#search=overscroll-behavior 我想它会在未来变得更好 (3认同)
  • 对于本身没有足够内容可滚动的内部元素,此策略似乎无效。 (3认同)
  • Safari不支持`overscroll-behavior`。 (2认同)

Dan*_* S. 11

有很多像这样的问题,有很多答案,但我找不到一个不涉及事件,脚本,插件等的令人满意的解决方案.我想在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>
Run Code Online (Sandbox Code Playgroud)

CSS:

#modal {
  position: absolute;
  height: 100px;
  width: 100px;
  top: 20%;
  left: 20%;
  overflow-y: scroll;
}
#parent {
  height: 4000px;
}
Run Code Online (Sandbox Code Playgroud)

2.没有父级滚动模态滚动

祖先最终滚动的原因是因为滚动事件起泡并且链上的某些元素能够处理它.阻止它的一种方法是确保链上没有任何元素知道如何处理滚动.就我们的例子而言,我们可以重构树以将模态移出父元素.由于不明原因,保留父级和模态DOM兄弟是不够的; 父级必须由另一个建立新堆叠上下文的元素包装.围绕父母的绝对定位包装可以做到这一点.

我们得到的结果是,只要模态接收到滚动事件,事件就不会冒泡到"父"元素.

通常应该可以重新设计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>
Run Code Online (Sandbox Code Playgroud)

CSS(仅限新):

#context {
  position: absolute;
  overflow-y: scroll;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

CSS(#2之上的新内容):

#overlay {
  background-color: transparent;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
Run Code Online (Sandbox Code Playgroud)

  • @ T4NK3R是的,有权衡.在我的情况下,重构DOM最好是将UI javascript抛到问题上.总的来说,我认为这是一个值得考虑从头开始设计树的人的选择. (2认同)

Boh*_*ets 9

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

CSS:

body.noscroll {
    overflow: hidden;
}
.scrollable {
    max-height: 200px;
    overflow-y: scroll;
    border: 1px solid #ccc;
}
Run Code Online (Sandbox Code Playgroud)

HTML:

<div class="scrollable">
...A bunch of items to make the div scroll...
</div>
...A bunch of text to make the body scroll...
Run Code Online (Sandbox Code Playgroud)

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');
          }
        });
Run Code Online (Sandbox Code Playgroud)

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

  • 在某些浏览器中隐藏的滚动条将所有页面向左移动,因为滚动消失... (5认同)

Jos*_*ush 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;
            }
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

用法

<div prevent-parent-scroll>
    ...
</div>
Run Code Online (Sandbox Code Playgroud)

希望这有助于下一个从谷歌搜索到这里的人.


sil*_*ind 8

这是一个简单的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);
Run Code Online (Sandbox Code Playgroud)


Pet*_*e B 7

使用带有mousewheel插件的delta值的本机元素滚动属性:

$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();
    }
});
Run Code Online (Sandbox Code Playgroud)


Gra*_*ant 7

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

.isolate-scrolling {
    overscroll-behavior: contain;
}
Run Code Online (Sandbox Code Playgroud)

如果鼠标将子元素留给父元素,这只会滚动父容器。


MK.*_*MK. 6

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

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

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

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

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