如何暂时禁用滚动?

538
我正在使用scrollTo jQuery插件,想知道是否有可能通过Javascript临时禁用窗口元素的滚动?我希望禁用滚动是因为在scrollTo动画过程中滚动会变得非常丑陋 ;)
当然,我可以执行$("body").css("overflow", "hidden");,并在动画停止后将其设置回auto,但更好的办法是滚动条仍然可见但不活动。

14
如果页面依然显示,用户就会认为它在运作。如果页面不滚动或没有响应,那么它将破坏用户对页面工作方式的心理模型,导致混乱。我建议找到更好的处理滚动和动画的方式,例如停止动画。 - Marcus Whybrow
另一种解决方案:https://dev59.com/1Gox5IYBdhLWcg3wVCxy - gdbj
47个回答

885

scroll事件无法被取消。但是你可以通过取消这些交互事件来做到:
与滚动相关的鼠标触摸滚动滚动按钮

[演示Demo]

// left: 37, up: 38, right: 39, down: 40,
// spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
var keys = {37: 1, 38: 1, 39: 1, 40: 1};

function preventDefault(e) {
  e.preventDefault();
}

function preventDefaultForScrollKeys(e) {
  if (keys[e.keyCode]) {
    preventDefault(e);
    return false;
  }
}

// modern Chrome requires { passive: false } when adding event
var supportsPassive = false;
try {
  window.addEventListener("test", null, Object.defineProperty({}, 'passive', {
    get: function () { supportsPassive = true; } 
  }));
} catch(e) {}

var wheelOpt = supportsPassive ? { passive: false } : false;
var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel';

// call this to Disable
function disableScroll() {
  window.addEventListener('DOMMouseScroll', preventDefault, false); // older FF
  window.addEventListener(wheelEvent, preventDefault, wheelOpt); // modern desktop
  window.addEventListener('touchmove', preventDefault, wheelOpt); // mobile
  window.addEventListener('keydown', preventDefaultForScrollKeys, false);
}

// call this to Enable
function enableScroll() {
  window.removeEventListener('DOMMouseScroll', preventDefault, false);
  window.removeEventListener(wheelEvent, preventDefault, wheelOpt); 
  window.removeEventListener('touchmove', preventDefault, wheelOpt);
  window.removeEventListener('keydown', preventDefaultForScrollKeys, false);
}

更新:使用被动监听器修复了 Chrome 桌面版和现代移动浏览器。


142
其他陷入同样困境的开发人员需要注意:确保删除所有尝试停止滚动的jQuery代码中的'e.stopPropagation()'调用,因为它不仅不起作用,而且会阻止事件冒泡到实际有效的代码。希望我的耗费了30分钟的经验可以帮助节省其他人的时间 :) - Dirk van Bergen
4
您可以将32(空格)添加到禁用键数组中(在代码注释中显示)。 - gblazex
23
我可以像往常一样使用鼠标中键滚动来浏览这个页面,所以这个技巧不会影响像我这样的用户 ;) - Denis V
16
滚动条未被禁用。 - verism
4
我可以通过将文件拖到页面顶部/底部来使其滚动。也可以选择页面上的文本,然后执行相同的拖拽操作来实现滚动。 - Eliseo Soto
显示剩余33条评论

500

只需将类添加到body即可:

.stop-scrolling {
  height: 100%;
  overflow: hidden;
}

添加类,然后在需要重新启用滚动时删除,已在IE,FF,Safari和Chrome中测试。

$('body').addClass('stop-scrolling')

对于移动设备,您需要处理 "touchmove" 事件:
$('body').bind('touchmove', function(e){e.preventDefault()})

取消绑定以重新启用滚动。在iOS6和Android 2.3.3上进行了测试。

$('body').unbind('touchmove')

4
好的!你需要处理 touchmove 事件,使用 $('body').bind('touchmove', function(e){e.preventDefault()})。我编辑了这个答案,包括了这个移动端的解决方案。 - MusikAnimal
85
虽然这种解决方案有效,但它有可能会产生不良影响,即页面会滚动回到顶部。 - Matt
3
除非我的选择器是 $('body,html')',否则这对我没有作用。 - PickYourPoison
31
这个解决方案可行,但是当你把这个class应用到body(例如)时,在Windows操作系统下会导致滚动条消失并创建一个“颠簸”的效果。 - Georgio
3
太棒了!我喜欢它的聪明和简单。让我克服其他人遇到的问题的方法是将 html 最初设置为 position: absolute; width: 100%; height: 100%; overflow-y: scroll。(我总是在 html 中添加 overflow-y: scroll,强烈推荐)。然后在 .stop-scrolling 中,仅在 html 上切换,你只需要添加 overflow-y: hidden。就这样,没有更多内容跳动了。(在 Chrome 43.0.2357.132 (64位) OS X Yosemite 中有效)。 - slamborne
显示剩余19条评论

76

这里是一个非常基本的方法:

window.onscroll = function () { window.scrollTo(0, 0); };

在IE6中有点卡顿。


21
不是真正的禁用,更像是当尝试滚动时恢复默认设置。 - Marcus Whybrow
14
对于不可取消的事件而言,情况已经尽可能好了。 - sdleihssirhc
4
虽然它并没有真正地禁用它,但它模拟了它,这对我来说已经足够好了。 - Chris Bier
14
在我看来-这里最佳的答案。此外,您不必将其锁定为“(0, 0)”,只需使用当前的滚动位置即可。 - YemSalat
8
但是我们如何重新启用它? - Cybernetic
显示剩余5条评论

72
以下解决方案是基本的但纯粹使用JavaScript(没有jQuery):
function disableScrolling(){
    var x=window.scrollX;
    var y=window.scrollY;
    window.onscroll=function(){window.scrollTo(x, y);};
}

function enableScrolling(){
    window.onscroll=function(){};
}

4
Safari 7.1和IE 11中存在跳动问题。最新版本的Chrome,Firefox和Opera可以正常使用。 - Timo Kähkönen
2
仍然在最新版本的Chrome上有一点点卡顿。 - Jason Allshorn
2
你可以设置 window.onscroll=null 而不是添加一个空的匿名函数,nullwindow.onload 的初始值。 - 27px
这对我来说最合适了!谢谢。 - Arslan Shahab
@LuckyLukeSkywalker 然后可以使用 addEventListener 和 removeEventListener,请查看此教程:(https://www.w3schools.com/jsref/met_element_removeeventlistener.asp) - 27px
显示剩余4条评论

27

非常抱歉回答一个老帖子,但我在寻找解决方案时遇到了这个问题。

有很多解决方法可以解决此问题以仍然显示滚动条,例如为容器提供100%的高度和overflow-y: scroll样式。

在我的情况下,我只是创建了一个带有滚动条的div,同时将overflow: hidden添加到body中以隐藏原生的滚动条:

function disableScroll() {
    document.getElementById('scrollbar').style.display = 'block';
    document.body.style.overflow = 'hidden';
}

滚动条元素必须具有以下样式:

overflow-y: scroll; top: 0; right: 0; display: none; height: 100%; position: fixed;

这显示了一个灰色的滚动条,希望对未来的访问者有所帮助。


100%整洁简单的技巧,最小化JS无滚动事件。只需定位我们需要禁用滚动的元素,并将溢出设置为隐藏。我写了近10年的JS和CSS,竟然没有想到这个!然后要重新启用滚动,只需切换溢出为自动! - Dale Ryan

25
这个解决方案在禁用滚动时会保持当前滚动位置,而不像某些解决方案会将用户弹回顶部。
它基于galambalazs的答案,但支持触摸设备,并重构为一个带有jQuery插件包装器的单一对象。 此处演示。 在github上查看。
/**
 * $.disablescroll
 * Author: Josh Harrison - aloof.co
 *
 * Disables scroll events from mousewheels, touchmoves and keypresses.
 * Use while jQuery is animating the scroll position for a guaranteed super-smooth ride!
 */

;(function($) {

    "use strict";

    var instance, proto;

    function UserScrollDisabler($container, options) {
        // spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
        // left: 37, up: 38, right: 39, down: 40
        this.opts = $.extend({
            handleKeys : true,
            scrollEventKeys : [32, 33, 34, 35, 36, 37, 38, 39, 40]
        }, options);

        this.$container = $container;
        this.$document = $(document);
        this.lockToScrollPos = [0, 0];

        this.disable();
    }

    proto = UserScrollDisabler.prototype;

    proto.disable = function() {
        var t = this;

        t.lockToScrollPos = [
            t.$container.scrollLeft(),
            t.$container.scrollTop()
        ];

        t.$container.on(
            "mousewheel.disablescroll DOMMouseScroll.disablescroll touchmove.disablescroll",
            t._handleWheel
        );

        t.$container.on("scroll.disablescroll", function() {
            t._handleScrollbar.call(t);
        });

        if(t.opts.handleKeys) {
            t.$document.on("keydown.disablescroll", function(event) {
                t._handleKeydown.call(t, event);
            });
        }
    };

    proto.undo = function() {
        var t = this;
        t.$container.off(".disablescroll");
        if(t.opts.handleKeys) {
            t.$document.off(".disablescroll");
        }
    };

    proto._handleWheel = function(event) {
        event.preventDefault();
    };

    proto._handleScrollbar = function() {
        this.$container.scrollLeft(this.lockToScrollPos[0]);
        this.$container.scrollTop(this.lockToScrollPos[1]);
    };

    proto._handleKeydown = function(event) {
        for (var i = 0; i < this.opts.scrollEventKeys.length; i++) {
            if (event.keyCode === this.opts.scrollEventKeys[i]) {
                event.preventDefault();
                return;
            }
        }
    };


    // Plugin wrapper for object
    $.fn.disablescroll = function(method) {

        // If calling for the first time, instantiate the object and save
        // reference. The plugin can therefore only be instantiated once per
        // page. You can pass options object in through the method parameter.
        if( ! instance && (typeof method === "object" || ! method)) {
            instance = new UserScrollDisabler(this, method);
        }

        // Instance already created, and a method is being explicitly called,
        // e.g. .disablescroll('undo');
        else if(instance && instance[method]) {
            instance[method].call(instance);
        }

    };

    // Global access
    window.UserScrollDisabler = UserScrollDisabler;

})(jQuery);

可能会有所帮助,如果禁用滚动按钮实际上起作用。 - cameronjonesweb
滚动条未被禁用。 - verism
@ZougenMoriver 是的,我已经切换了它。我可以在Firefox 33.0.3中验证这种行为。尽管箭头键等不起作用,但滚动条确实可以。我还在使用Chrome(在工作中-我忘记了哪个版本)进行了相同结果的测试。 - verism
很棒的插件!我想知道是否有可能在滚动被禁用时检查滚轮是否已被触发?我问这个问题是因为我希望在开始时禁用滚动,然后如果用户向下使用滚轮,则平滑滚动到底部。谢谢! - adamj
1
请忽略,我发现将其绑定到“mousewheel”上非常有效。 - adamj
显示剩余10条评论

22
var winX = null;
var winY = null;

window.addEventListener('scroll', function () {
    if (winX !== null && winY !== null) {
        window.scrollTo(winX, winY);
    }
});

function disableWindowScroll() {
    winX = window.scrollX;
    winY = window.scrollY;
}

function enableWindowScroll() {
    winX = null;
    winY = null;
}

为了类型安全,我建议使用负数而不是null。 - Sergei Kovalenko

8
这个答案提供了一个解决办法,用于消除应用这个方案中所建议的overflow: hidden时出现的“凸起”问题。由于编辑被拒绝,这里是示例:

为了消除应用overflow: hidden时出现的“凸起”问题,你可以计算滚动条的宽度并将其替换为边距。以下是对body元素的示例:

const bodyScrollControls = {
  scrollBarWidth: window.innerWidth - document.body.clientWidth,

  disable() {
    document.body.style.marginRight = `${this.scrollBarWidth}px`;
    document.body.style.overflowY = 'hidden';
  },
  enable() {
    document.body.style.marginRight = null;
    document.body.style.overflowY = null;
  },
};

如果一个元素已经有了 margin-right,那么获取现有的值并加上滚动条的宽度不应该成为问题。

8
我正在寻找一个解决方案来解决这个问题,但以上任何解决方案都不能令我满意(截至撰写本回答),因此我想出了这个解决方案。
CSS
.scrollDisabled {   
    position: fixed;
    margin-top: 0;// override by JS to use acc to curr $(window).scrollTop()
    width: 100%;
}

JS

var y_offsetWhenScrollDisabled=0;

function disableScrollOnBody(){
    y_offsetWhenScrollDisabled= $(window).scrollTop();
    $('body').addClass('scrollDisabled').css('margin-top', -y_offsetWhenScrollDisabled);
}
function enableScrollOnBody(){
    $('body').removeClass('scrollDisabled').css('margin-top', 0);
    $(window).scrollTop(y_offsetWhenScrollDisabled);
}

如果我在.scrollDisabled样式中添加了overflow-y: scroll,我发现这对我有用。否则,每次滚动条隐藏时都会出现一点跳动。当然,这仅适用于我,因为即使在最大的显示器上,我的页面也足够长需要滚动条。 - Andrew Miner

7

从Chrome 56版本开始以及其他现代浏览器,您需要在addEventListener调用中添加passive:false才能使preventDefault生效。因此,我使用以下代码来停止移动设备上的滚动:

function preventDefault(e){
    e.preventDefault();
}

function disableScroll(){
    document.body.addEventListener('touchmove', preventDefault, { passive: false });
}
function enableScroll(){
    document.body.removeEventListener('touchmove', preventDefault, { passive: false });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接