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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5802467/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 18:57:02  来源:igfitidea点击:

Prevent scrolling of parent element when inner element scroll position reaches top/bottom?

javascriptjqueryscrollevent-bubblingevent-propagation

提问by T4NK3R

I have a little "floating tool box" - a div with position:fixed; overflow:auto. Works just fine.

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

But when scrolling inside that box (with the mouse wheel) and reaching the bottom OR top, the parent element "takes over" the "scroll request" : The document behind the tool box scrolls.
- Which is annoying and not what the user "asked for".

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

I'm using jQuery and thought I could stop this behaviour with event.stoppropagation():
$("#toolBox").scroll( function(event){ event.stoppropagation() });

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

It does enter the function, but still, propagation happens anyway (the document scrolls)
- It's surprisingly hard to search for this topic on SO (and Google), so I have to ask:
How to prevent propagation / bubbling of the scroll-event ?

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

Edit:
Working solution thanks to amustill (and Brandon Aaron for the mousewheel-plugin here:
https://github.com/brandonaaron/jquery-mousewheel/raw/master/jquery.mousewheel.js

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

采纳答案by amustill

It's possible with the use of Brandon Aaron's Mousewheel plugin.

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

Here's a demo: http://jsbin.com/jivutakama/edit?html,js,output

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

回答by Troy Alford

I am adding this answer for completeness because the accepted answer by @amustill does not correctly solve the problem in Internet Explorer. Please see the comments in my original postfor details. In addition, this solution does not require any plugins - only jQuery.

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

In essence, the code works by handling the mousewheelevent. Each such event contains a wheelDeltaequal to the number of pxwhich it is going to move the scrollable area to. If this value is >0, then we are scrolling up. If the wheelDeltais <0then we are scrolling down.

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

FireFox: FireFox uses DOMMouseScrollas the event, and populates originalEvent.detail, whose +/-is reversed from what is described above. It generally returns intervals of 3, while other browsers return scrolling in intervals of 120(at least on my machine). To correct, we simply detect it and multiply by -40to normalize.

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

@amustill's answerworks by canceling the event if the <div>'s scrollable area is already either at the top or the bottom maximum position. However, Internet Explorerdisregards the canceled event in situations where the deltais larger than the remaining scrollable space.

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

In other words, if you have a 200pxtall <div>containing 500pxof scrollable content, and the current scrollTopis 400, a mousewheelevent which tells the browser to scroll 120pxfurther will result in both the <div>and the <body>scrolling, because 400+ 120> 500.

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

So - to solve the problem, we have to do something slightly different, as shown below:

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

The requisite jQuerycode is:

必要的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();
    }
});

In essence, this code cancels any scrolling event which would create the unwanted edge condition, then uses jQuery to set the scrollTopof the <div>to either the maximum or minimum value, depending on which direction the mousewheelevent was requesting.

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

Because the event is canceled entirely in either case, it never propagates to the bodyat all, and therefore solves the issue in IE, as well as all of the other browsers.

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

I have also put up a working example on jsFiddle.

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

回答by raina77ow

All the solutions given in this thread don't mention an existing - and native - way to solve this problem without reordering DOM and/or using event preventing tricks. But there's a good reason: this way is proprietary - and available on MS web platform only. Quoting MSDN:

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

-ms-scroll-chainingproperty - specifies the scrolling behavior that occurs when a user hits the scroll limit during a manipulation. Property values:

chained- Initial value. The nearest scrollable parent element begins scrolling when the user hits a scroll limit during a manipulation. No bounce effect is shown.

none- A bounce effect is shown when the user hits a scroll limit during a manipulation.

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

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

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

Granted, this property is supported on IE10+/Edge only. Still, here's a telling quote:

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

To give you a sense of how popular preventing scroll chaining may be, according to my quick http-archive search "-ms-scroll-chaining: none" is used in 0.4% of top 300K pages despite being limited in functionality and only supported on IE/Edge.

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

And now good news, everyone! Starting from Chrome 63, we finally have a native cure for Blink-based platforms too - and that's both Chrome (obviously) and Android WebView (soon).

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

Quoting the introducing article:

引用介绍文章

The overscroll-behaviorproperty is a new CSS feature that controls the behavior of what happens when you over-scroll a container (including the page itself). You can use it to cancel scroll chaining, disable/customize the pull-to-refresh action, disable rubberbanding effects on iOS (when Safari implements overscroll-behavior), and more.[...]

The property takes three possible values:

auto- Default. Scrolls that originate on the element may propagate to ancestor elements.

contain- prevents scroll chaining. Scrolls do not propagate to ancestors but local effects within the node are shown. For example, the overscroll glow effect on Android or the rubberbanding effect on iOS which notifies the user when they've hit a scroll boundary. Note: using overscroll-behavior: contain on the html element prevents overscroll navigation actions.

none- same as contain but it also prevents overscroll effects within the node itself (e.g. Android overscroll glow or iOS rubberbanding).

[...] The best part is that using overscroll-behavior does not adversely affect page performance like the hacks mentioned in the intro!

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

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

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

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

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

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

Here's this feature in action. And here's corresponding CSS Module document.

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

UPDATE:Firefox, since version 59, has joined the club, and MS Edge is expected to implement this feature in version 18. Here's the corresponding caniusage.

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

回答by Nebril

I know it's quite an old question, but since this is one of top results in google... I had to somehow cancel scroll bubbling without jQuery and this code works for me:

我知道这是一个很老的问题,但由于这是谷歌的顶级结果之一......我不得不以某种方式取消没有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);
}

回答by dOxxx

EDIT: CodePen example

编辑:CodePen 示例

For AngularJS, I defined the following directive:

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

And then added it to the scrollable element (the dropdown-menu ul):

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

Tested on Chrome and Firefox. Chrome's smooth scrolling defeats this hack when a large mousewheel movement is made near (but not at) the top or bottom of the scroll region.

在 Chrome 和 Firefox 上测试。当在滚动区域的顶部或底部附近(但不是在)附近进行大的鼠标滚轮移动时,Chrome 的平滑滚动会阻止这种攻击。

回答by Daniel S.

There are tons of questions like this out there, with many answers, but I could not find a satisfactory solution that did not involve events, scripts, plugins, etc. I wanted to keep it straight in HTML and CSS. I finally found a solution that worked, although it involved restructuring the markup to break the event chain.

像这样的问题有很多,有很多答案,但我找不到一个不涉及事件、脚本、插件等的令人满意的解决方案。我想在 HTML 和 CSS 中保持直截了当。我终于找到了一个有效的解决方案,尽管它涉及重构标记以打破事件链。



1. Basic problem

一、基本问题

Scrolling input (i.e.: mousewheel) applied to the modal element will spill over into an ancestor element and scroll it in the same direction, if some such element is scrollable:

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

(All examples are meant to be viewed on desktop resolutions)

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

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

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

HTML:

HTML:

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

CSS:

CSS:

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


2. No parent scroll on modal scroll

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

The reason why the ancestor ends up scrolling is because the scroll event bubbles and some element on the chain is able tohandle it. A way to stop that is to make sure none of the elements on the chain know how to handle the scroll. In terms of our example, we can refactor the tree to move the modal out of the parent element. For obscure reasons, it is not enough to keep the parent and the modal DOM siblings; the parent must be wrapped by another element that establishes a new stacking context. An absolutely positioned wrapper around the parent can do the trick.

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

The result we get is that as long as the modal receives the scroll event, the event will not bubble to the "parent" element.

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

It should typically be possible to redesign the DOM tree to support this behavior without affecting what the end user sees.

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

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

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

HTML:

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 (new only):

CSS(仅限新):

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


3. No scroll anywhere except in modal while it is up

3.除了模态之外的任何地方都没有滚动

The solution above still allows the parent to receive scroll events, as long as they are not intercepted by the modal window (i.e. if triggered by mousewheel while the cursor is not over the modal). This is sometimes undesirable and we may want to forbid all background scrolling while the modal is up. To do that, we need to insert an extra stacking context that spans the whole viewport behind the modal. We can do that by displaying an absolutely positioned overlay, which can be fully transparent if necessary (but not visibility:hidden).

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

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

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

HTML:

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 (new on top of #2):

CSS(在#2之上新增):

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

回答by Jossef Harush

Angular JS Directive

Angular JS 指令

I had to wrap an angular directive. The following is a Mashup of the other answers here. tested on Chrome and Internet Explorer 11.

我不得不包装一个角度指令。以下是此处其他答案的混搭。在 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;
            }
        }
    }
});

Usage

用法

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


Hopes this helps the next person that gets here from a Google search.

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

回答by Bohdan Lyzanets

As variant, to avoid performance issues with scrollor mousewheelhandling, you can use code like below:

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

css:

css:

body.noscroll {
    overflow: hidden;
}
.scrollable {
    max-height: 200px;
    overflow-y: scroll;
    border: 1px solid #ccc;
}

html:

html:

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

js:

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');
          }
        });

Example of work: http://jsbin.com/damuwinarata/4

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

回答by silverwind

Here's a plain JavaScript version:

这是一个简单的 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);

回答by Pete B

Using native element scroll properties with the delta value from the mousewheel plugin:

使用带有来自 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();
    }
});