防止父元素的滚动?
我有一个“浮动工具箱” – 一个position:fixed; overflow:auto
的div position:fixed; overflow:auto
position:fixed; overflow:auto
。 工作得很好。
但是,当滚动到该框内(用鼠标滚轮)并到达底部或顶部时,父元素“接pipe”“滚动请求”:工具框后面的文档滚动。
– 这是烦人的,而不是用户“要求”。
我正在使用jQuery,并认为我可以用event.stoppropagation()停止这种行为:
$("#toolBox").scroll( function(event){ event.stoppropagation() });
它确实进入了这个function,但是仍然会传播(文档滚动)
– 在SO(和Google)上search这个主题是非常困难的,所以我必须问:
如何防止滚动事件的传播/冒泡?
编辑:
工作解决scheme感谢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(); } } });
使用Brandon Aaron的Mousewheel插件是可能的。
这是一个演示: http : //jsbin.com/jivutakama/edit?html,js,output
我正在添加此答案的完整性,因为@amustill接受的答案不正确地解决Internet Explorer中的问题 。 有关详细信息,请参阅原始post中的评论。 另外,这个解决scheme不需要任何插件 – 只有jQuery。
本质上,代码通过处理mousewheel
事件来工作。 每个这样的事件都包含一个wheelDelta
,它等于它要将可滚动区域移动到的px
的数量。 如果这个值>0
,那么我们正在滚动。 如果wheelDelta
<0
那么我们正在down
滚动。
FireFox :FireFox使用DOMMouseScroll
作为事件,并填充originalEvent.detail
,其+/-
与上面描述的相反。 它通常返回间隔3
,而其他浏览器返回滚动间隔120
(至less在我的机器上)。 为了更正,我们只需检测它并乘以-40
来正常化。
如果<div>
的可滚动区域已经位于顶部或底部的最大位置, @ amustill的答案将取消该事件。 但是,在delta
大于剩余的可滚动空间的情况下, Internet Explorer忽略取消的事件。
换句话说,如果你有一个200px
高的<div>
包含500px
的可滚动内容,当前scrollTop
是400
, mousewheel
事件告诉浏览器进一步滚动120px
会导致<div>
和<body>
滚动,因为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将<div>
的scrollTop
设置为最大值或最小值,具体取决于mousewheel
事件请求的方向。
因为事件在任何情况下都被完全取消,所以它永远不会传播到body
,因此解决了IE以及其他所有浏览器的问题。
我也在jsFiddle上做了一个实例 。
我知道这是一个相当古老的问题,但因为这是谷歌的顶级成绩之一…我不得不以某种方式取消没有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); }
对于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上testing 在滚动区域的顶部或底部附近(但不是在该区域的顶部或底部)进行大的鼠标滚轮移动时,Chrome的平滑滚动可以击败此黑客攻击。
作为变体,为了避免scroll
或mousewheel
处理的性能问题,您可以使用如下代码:
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
使用本地元素滚动属性和mousewheel插件中的增量值:
$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(); } });
如果有人仍然在寻找这个解决scheme,下面的插件做这个工作http://mohammadyounes.github.io/jquery-scrollLock/
它完全解决了在给定容器内locking鼠标滚轮的问题,防止它传播到父元素。
它不会改变滚轮的滚动速度,用户体验不会受到影响。 不pipe操作系统鼠标滚轮的垂直滚动速度如何(在Windows上,它可以设置为一个屏幕或每行最多100行)。
演示: http : //mohammadyounes.github.io/jquery-scrollLock/example/
来源: https : //github.com/MohammadYounes/jquery-scrollLock
Angular JS指令
我不得不包装一个angular度指令。 以下是这里其他答案的混搭。 在Chrome和Internet Explorer 11上进行testing。
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>
希望这有助于下一个从谷歌search到这里的人。
有很多像这样的问题,有很多答案,但我找不到一个令人满意的解决scheme,不涉及事件,脚本,插件等。我想保持它在HTML和CSS直。 我终于find了一个可行的解决scheme,虽然它涉及重组标记以打破事件链。
1.基本问题
应用于模态元素的滚动input(即:鼠标滚轮)将溢出到祖先元素中,并沿同一方向滚动,如果某些元素是可滚动的:
(所有的例子都是用桌面分辨率来看的)
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兄弟是不够的; 父级必须由build立新的堆叠上下文的另一个元素包装。 一个绝对定位的包装父母可以做的伎俩。
我们得到的结果是只要模态接收到滚动事件,事件就不会冒泡到“父”元素。
通常应该重新deviseDOM树来支持这种行为,而不会影响最终用户看到的内容。
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.除了在模态中时,不会滚动到任何地方
上面的解决scheme仍然允许父级接收滚动事件,只要它们不被模式窗口拦截(即如果光标不在模态上,则通过鼠标滚轮触发)。 这有时是不受欢迎的,我们可能希望在模式启动时禁止所有的背景滚动。 要做到这一点,我们需要插入一个额外的堆叠上下文,跨越模态后面的整个视口。 我们可以通过显示一个绝对定位的覆盖图来实现,如果需要,它可以是完全透明的(但不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; }
这是一个普通的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);
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(); } } }); } };
这实际上在AngularJS中有效。 在Chrome和Firefox上testing
.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(); } }); } }; })
上面的方法是不自然的,一些谷歌search后,我find了一个更好的解决scheme,而不需要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; }
对于那些使用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值,这就是高度= 2的值。
基本上不同的是,在MooTools中,增量信息来自event.wheel,而不是传递给事件的额外参数。
另外,如果我把这个代码绑定到任何东西上,我遇到了问题(对于绑定函数,event.target.scrollHeight对于非绑定函数不等于this.scrollHeight)
希望这可以帮助像这个职位帮助我的人;)
我的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; }); }
新的web开发在这里。 这对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是很简单的。
我从所选的图书馆中find了这个: 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)
这对我有用。
我讨厌necro张贴,但我正在寻找这个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);
jQuery插件与模拟自然滚动的Internet Explorer
$.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); } } }); }); };
我能find的最好的解决scheme是侦听窗口上的滚动事件,并将scrollTop设置为前一个scrollTop,如果子div可见的话。
prevScrollPos = 0 $(window).scroll (ev) -> if $('#mydiv').is(':visible') document.body.scrollTop = prevScrollPos else prevScrollPos = document.body.scrollTop
如果你发动了很多滚动事件,那么在子div的背景中会有一个闪烁,所以可以调整它,但是很less被注意到,这对我的用例来说已经足够了。
我有类似的情况,这是我如何解决它:
我所有的可滚动元素都可以滚动 。
$(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()确保不从可滚动的子区域滚动父可滚动区域。
下面是它的一个vanilla JS实现: http : //jsbin.com/lugim/2/edit? js, output
不要使用overflow: hidden;
在body
。 它会自动将所有内容滚动到顶部。 也不需要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; }
在这里玩演示。
请查看Leland Kwong的代码。
基本的想法是绑定重复事件到子元素,然后使用本地javascript属性的scrollHeight
和子元素的jquery属性outerHeight
来检测滚动的结束,在此之上return false
以避免滚动事件。
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; } });
当鼠标hover在可滚动元素上时,还有一个有趣的技巧来locking父级的scrollTop
。 这样你就不必实现自己的轮子滚动。
Here's an example for preventing document scroll, but it can be adjusted for any element.
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'); });
MK offered a great plugin in his answer. Plugin can be found here . However, for the sake of completion, I thought it'd be a good idea to put it together in one answer for AngularJS.
-
Start by injecting the bower or npm (whichever is preferred)
bower install jquery-scrollLock --save npm install jquery-scroll-lock --save
-
Add the following directive. I am choosing to add it as an attribute
(function() { 'use strict'; angular .module('app') .directive('isolateScrolling', isolateScrolling); function isolateScrolling() { return { restrict: 'A', link: function(sc, elem, attrs) { $('.scroll-container').scrollLock(); } } } })();
-
And the important piece the plugin fails to document in their website is the HTML structure that it must follow.
<div class="scroll-container locked"> <div class="scrollable" isolate-scrolling> ... whatever ... </div> </div>
The attribute isolate-scrolling
must contain the scrollable
class and it all needs to be inside the scroll-container
class or whatever class you choose and the locked
class must be cascaded.
Simple solution with mouseweel event:
$('.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(); } });
You can try it this way:
$('#element').on('shown', function(){ $('body').css('overflow-y', 'hidden'); $('body').css('margin-left', '-17px'); }); $('#element').on('hide', function(){ $('body').css('overflow-y', 'scroll'); $('body').css('margin-left', '0px'); });