33

我用 jQueryMobile 为 iOS 和 Android 创建了一个网站。

我不希望文档本身滚动。相反,只有一个区域(一个<div>元素)应该是可滚动的(通过 css 属性overflow-y:scroll)。

所以我通过以下方式禁用了文档滚动:

$(document).bind("touchstart", function(e){
    e.preventDefault();
});

$(document).bind("touchmove", function(e){
    e.preventDefault();
});

但这也将禁用文档中所有其他元素的滚动,无论是否overflow:scroll设置。

我该如何解决这个问题?

4

11 回答 11

24

这个仅限 CSS的解决方案怎么样:

https://jsfiddle.net/Volker_E/jwGBy/24/

body获取position: fixed;以及您希望的所有其他元素overflow: scroll;。适用于移动 Chrome (WebKit)/Firefox 19/Opera 12。

您还将看到我对 jQuery 解决方案的各种尝试。但是,一旦您将touchmove/绑定touchstart到文档,无论是否未绑定,它都会阻碍子 div 中的滚动。

免责声明:这个问题的解决方案在很多方面基本上不是很好的用户体验!您永远不会知道访问者的视口到底有多大或他们使用的字体大小(例如客户端用户代理样式),因此很容易将重要内容隐藏在您的文档中。

于 2013-02-28T14:41:42.390 回答
12

最后,我让它工作了。真的很简单:

var $layer = $("#layer");
$layer.bind('touchstart', function (ev) {
    var $this = $(this);
    var layer = $layer.get(0);

    if ($this.scrollTop() === 0) $this.scrollTop(1);
    var scrollTop = layer.scrollTop;
    var scrollHeight = layer.scrollHeight;
    var offsetHeight = layer.offsetHeight;
    var contentHeight = scrollHeight - offsetHeight;
    if (contentHeight == scrollTop) $this.scrollTop(scrollTop-1);
});
于 2013-03-22T23:37:25.310 回答
12

也许我误解了这个问题,但如果我是正确的:

您希望除某个元素外无法滚动,因此您:

$(document).bind("touchmove", function(e){
    e.preventDefault();
});

防止文档中的所有内容。


你为什么不停止你想要滚动的元素上的事件冒泡呢? (PS:您不必阻止 touchstart -> 如果您使用 touch start 来选择元素而不是也阻止单击,则仅需要触摸移动,因为它实际上是在跟踪移动)

$('#element').on('touchmove', function (e) {
     e.stopPropagation();
});

现在在元素 CSS

#element {
   overflow-y: scroll; // (vertical) 
   overflow-x: hidden; // (horizontal)
}

如果您使用的是移动设备,您甚至可以更进一步。您可以强制硬件加速滚动(尽管并非所有移动浏览器都支持此功能);

Browser Overflow scroll:

Android Browser Yes
Blackberry Browser  Yes
Chrome for Mobile   Yes
Firefox Mobile  Yes
IE Mobile           Yes
Opera Mini          No
Opera Mobile    Kinda
Safari          Yes

#element.nativescroll {
    -webkit-overflow-scrolling: touch;
}

普通的:

<div id="element"></div>

原生感觉:

<div id="element" class="nativescroll"></div>
于 2014-05-17T19:45:27.347 回答
3

这是我正在使用的解决方案:

$scrollElement 是滚动元素,$scrollMask 是一个带有 style 的 div position: fixed; top: 0; bottom: 0;。$ scrollMaskz-index小于 $scrollElement。

$scrollElement.on('touchmove touchstart', function (e) {
    e.stopPropagation();
});
$scrollMask.on('touchmove', function(e) {
    e.stopPropagation();
    e.preventDefault();
});
于 2014-08-13T07:44:47.623 回答
3

我正在寻找一种不需要调用应该滚动的特定区域的解决方案。拼凑一些资源,这对我有用:

    // Detects if element has scroll bar
    $.fn.hasScrollBar = function() {
        return this.get(0).scrollHeight > this.outerHeight();
    }

    $(document).on("touchstart", function(e) {
        var $scroller;
        var $target = $(e.target);

        // Get which element could have scroll bars
        if($target.hasScrollBar()) {
            $scroller = $target;
        } else {
            $scroller = $target
                .parents()
                .filter(function() {
                    return $(this).hasScrollBar();
                })
                .first()
            ;
        }

        // Prevent if nothing is scrollable
        if(!$scroller.length) {
            e.preventDefault();
        } else {
            var top = $scroller[0].scrollTop;
            var totalScroll = $scroller[0].scrollHeight;
            var currentScroll = top + $scroller[0].offsetHeight;

            // If at container edge, add a pixel to prevent outer scrolling
            if(top === 0) {
                $scroller[0].scrollTop = 1;
            } else if(currentScroll === totalScroll) {
                $scroller[0].scrollTop = top - 1;
            }
        }
    });

此代码需要 jQuery。

资料来源:


更新

我需要一个原生的 JavaScript 版本,所以下面是修改后的版本。我实现了一个边距检查器和一些明确允许输入/文本区域可点击的东西(我在我使用它的项目中遇到了这个问题......你的项目可能不需要它)。请记住,这是 ES6 代码。

const preventScrolling = e => {
    const shouldAllowEvent = element => {
        // Must be an element that is not the document or body
        if(!element || element === document || element === document.body) {
            return false;
        }

        // Allow any input or textfield events
        if(['INPUT', 'TEXTAREA'].indexOf(element.tagName) !== -1) {
            return true;
        }

        // Get margin and outerHeight for final check
        const styles = window.getComputedStyle(element);
        const margin = parseFloat(styles['marginTop']) +
            parseFloat(styles['marginBottom']);
        const outerHeight = Math.ceil(element.offsetHeight + margin);

        return (element.scrollHeight > outerHeight) && (margin >= 0);
    };

    let target = e.target;

    // Get first element to allow event or stop
    while(target !== null) {
        if(shouldAllowEvent(target)) {
            break;
        }

        target = target.parentNode;
    }

    // Prevent if no elements
    if(!target) {
        e.preventDefault();
    } else {
        const top = target.scrollTop;
        const totalScroll = target.scrollHeight;
        const currentScroll = top + target.offsetHeight;

        // If at container edge, add a pixel to prevent outer scrolling
        if(top === 0) {
            target.scrollTop = 1;
        } else if(currentScroll === totalScroll) {
            target.scrollTop = top - 1;
        }
    }
};

document.addEventListener('touchstart', preventScrolling);
document.addEventListener('mousedown', preventScrolling);
于 2015-04-16T20:00:53.477 回答
2

就我而言,我有一个可滚动的主体和一个可滚动的浮动菜单。两者都必须是可滚动的,但是当“浮动菜单”(位置:固定)接收到触摸事件并且正在滚动并到达顶部或底部时,我必须阻止正文滚动。默认情况下,浏览器开始滚动正文。

我真的很喜欢jimmont 的回答,但不幸的是,它在所有设备和浏览器上都不能正常工作,尤其是在快速和长时间滑动时。

我最终在浮动菜单上使用了MOMENTUM SCROLLING USING JQUERY (hnldesign.nl),它可以防止默认浏览器滚动,然后动画滚动本身。为了完整起见,我在此处包含该代码:

/**
 * jQuery inertial Scroller v1.5
 * (c)2013 hnldesign.nl
 * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
 **/
/*jslint browser: true*/
/*global $, jQuery*/

/* SETTINGS */
var i_v = {
    i_touchlistener     : '.inertialScroll',         // element to monitor for touches, set to null to use document. Otherwise use quotes. Eg. '.myElement'. Note: if the finger leaves this listener while still touching, movement is stopped.
    i_scrollElement     : '.inertialScroll',         // element (class) to be scrolled on touch movement
    i_duration          : window.innerHeight * 1.5, // (ms) duration of the inertial scrolling simulation. Devices with larger screens take longer durations (phone vs tablet is around 500ms vs 1500ms). This is a fixed value and does not influence speed and amount of momentum.
    i_speedLimit        : 1.2,                      // set maximum speed. Higher values will allow faster scroll (which comes down to a bigger offset for the duration of the momentum scroll) note: touch motion determines actual speed, this is just a limit.
    i_handleY           : true,                     // should scroller handle vertical movement on element?
    i_handleX           : true,                     // should scroller handle horizontal movement on element?
    i_moveThreshold     : 100,                      // (ms) determines if a swipe occurred: time between last updated movement @ touchmove and time @ touchend, if smaller than this value, trigger inertial scrolling
    i_offsetThreshold   : 30,                       // (px) determines, together with i_offsetThreshold if a swipe occurred: if calculated offset is above this threshold
    i_startThreshold    : 5,                        // (px) how many pixels finger needs to move before a direction (horizontal or vertical) is chosen. This will make the direction detection more accurate, but can introduce a delay when starting the swipe if set too high
    i_acceleration      : 0.5,                      // increase the multiplier by this value, each time the user swipes again when still scrolling. The multiplier is used to multiply the offset. Set to 0 to disable.
    i_accelerationT     : 250                       // (ms) time between successive swipes that determines if the multiplier is increased (if lower than this value)
};
/* stop editing here */

//set some required vars
i_v.i_time  = {};
i_v.i_elem  = null;
i_v.i_elemH = null;
i_v.i_elemW = null;
i_v.multiplier = 1;

// Define easing function. This is based on a quartic 'out' curve. You can generate your own at http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
if ($.easing.hnlinertial === undefined) {
    $.easing.hnlinertial = function (x, t, b, c, d) {
        "use strict";
        var ts = (t /= d) * t, tc = ts * t;
        return b + c * (-1 * ts * ts + 4 * tc + -6 * ts + 4 * t);
    };
}

$(i_v.i_touchlistener || document)
    .on('touchstart touchmove touchend', function (e) {
        "use strict";
        //prevent default scrolling
        e.preventDefault();
        //store timeStamp for this event
        i_v.i_time[e.type]  = e.timeStamp;
    })
    .on('touchstart', function (e) {
        "use strict";
        this.tarElem = $(e.target);
        this.elemNew = this.tarElem.closest(i_v.i_scrollElement).length > 0 ? this.tarElem.closest(i_v.i_scrollElement) : $(i_v.i_scrollElement).eq(0);
        //dupecheck, optimizes code a bit for when the element selected is still the same as last time
        this.sameElement = i_v.i_elem ? i_v.i_elem[0] == this.elemNew[0] : false;
        //no need to redo these if element is unchanged
        if (!this.sameElement) {
            //set the element to scroll
            i_v.i_elem = this.elemNew;
            //get dimensions
            i_v.i_elemH = i_v.i_elem.innerHeight();
            i_v.i_elemW = i_v.i_elem.innerWidth();
            //check element for applicable overflows and reevaluate settings
            this.i_scrollableY      = !!((i_v.i_elemH < i_v.i_elem.prop('scrollHeight') && i_v.i_handleY));
            this.i_scrollableX    = !!((i_v.i_elemW < i_v.i_elem.prop('scrollWidth') && i_v.i_handleX));
        }
        //get coordinates of touch event
        this.pageY      = e.originalEvent.touches[0].pageY;
        this.pageX      = e.originalEvent.touches[0].pageX;
        if (i_v.i_elem.is(':animated') && (i_v.i_time.touchstart - i_v.i_time.touchend) < i_v.i_accelerationT) {
            //user swiped while still animating, increase the multiplier for the offset
            i_v.multiplier += i_v.i_acceleration;
        } else {
            //else reset multiplier
            i_v.multiplier = 1;
        }
        i_v.i_elem
            //stop any animations still running on element (this enables 'tap to stop')
            .stop(true, false)
            //store current scroll positions of element
            .data('scrollTop', i_v.i_elem.scrollTop())
            .data('scrollLeft', i_v.i_elem.scrollLeft());
    })
    .on('touchmove', function (e) {
        "use strict";
        //check if startThreshold is met
        this.go = (Math.abs(this.pageX - e.originalEvent.touches[0].pageX) > i_v.i_startThreshold || Math.abs(this.pageY - e.originalEvent.touches[0].pageY) > i_v.i_startThreshold);
    })
    .on('touchmove touchend', function (e) {
        "use strict";
        //check if startThreshold is met
        if (this.go) {
            //set animpar1 to be array
            this.animPar1 = {};
            //handle events
            switch (e.type) {
            case 'touchmove':
                this.vertical       = Math.abs(this.pageX - e.originalEvent.touches[0].pageX) < Math.abs(this.pageY - e.originalEvent.touches[0].pageY); //find out in which direction we are scrolling
                this.distance       = this.vertical ? this.pageY - e.originalEvent.touches[0].pageY : this.pageX - e.originalEvent.touches[0].pageX; //determine distance between touches
                this.acc            = Math.abs(this.distance / (i_v.i_time.touchmove - i_v.i_time.touchstart)); //calculate acceleration during movement (crucial)
                //determine which property to animate, reset animProp first for when no criteria is matched
                this.animProp       = null;
                if (this.vertical && this.i_scrollableY) { this.animProp = 'scrollTop'; } else if (!this.vertical && this.i_scrollableX) { this.animProp = 'scrollLeft'; }
                //set animation parameters
                if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance; }
                this.animPar2       = { duration: 0 };
                break;
            case 'touchend':
                this.touchTime      = i_v.i_time.touchend - i_v.i_time.touchmove; //calculate touchtime: the time between release and last movement
                this.i_maxOffset    = (this.vertical ? i_v.i_elemH : i_v.i_elemW) * i_v.i_speedLimit; //(re)calculate max offset
                //calculate the offset (the extra pixels for the momentum effect
                this.offset         = Math.pow(this.acc, 2) * (this.vertical ? i_v.i_elemH : i_v.i_elemW);
                this.offset         = (this.offset > this.i_maxOffset) ? this.i_maxOffset : this.offset;
                this.offset         = (this.distance < 0) ? -i_v.multiplier * this.offset : i_v.multiplier * this.offset;
                //if the touchtime is low enough, the offset is not null and the offset is above the offsetThreshold, (re)set the animation parameters to include momentum
                if ((this.touchTime < i_v.i_moveThreshold) && this.offset !== 0 && Math.abs(this.offset) > (i_v.i_offsetThreshold)) {
                    if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance + this.offset; }
                    this.animPar2   = { duration: i_v.i_duration, easing : 'hnlinertial', complete: function () {
                        //reset multiplier
                        i_v.multiplier = 1;
                    }};
                }
                break;
            }

            // run the animation on the element
            if ((this.i_scrollableY || this.i_scrollableX) && this.animProp) {
                i_v.i_elem.stop(true, false).animate(this.animPar1, this.animPar2);
            }
        }
    });

另一个观察:我还在 touchmove 事件中尝试了菜单 div 上的 e.stopPropagation() 和 window/body 上的 e.preventDefault() 的各种组合,但没有成功,我只设法阻止我想要的滚动而不是我不想要的滚动. 我还尝试在整个文档上设置一个 div,在文档和菜单之间使用 z-index,仅在 touchstart 和 touchend 之间可见,但它没有收到 touchmove 事件(因为它在菜单 div 下)。

于 2014-09-29T08:22:07.970 回答
0

这是一个使用 jQuery 处理事件的解决方案。

var stuff = {};
$('#scroller').on('touchstart',stuff,function(e){
  e.data.max = this.scrollHeight - this.offsetHeight;
  e.data.y = e.originalEvent.pageY;
}).on('touchmove',stuff,function(e){
  var dy = e.data.y - e.originalEvent.pageY;
  // if scrolling up and at the top, or down and at the bottom
  if((dy < 0 && this.scrollTop < 1)||(dy > 0 && this.scrollTop >= e.data.max)){
    e.preventDefault();
  };
});
于 2013-07-31T19:19:56.287 回答
0

首先将 innerScroller 放置在屏幕上任何您想要的位置,然后通过将其 css 设置为“隐藏”来修复 outerScroller。当您想恢复它时,您可以将其设置回“自动”或“滚动”,无论您以前使用过哪个。

于 2015-05-27T18:13:18.227 回答
0

这是我在触摸设备和笔记本电脑上的实现。

function ScrollManager() {
    let startYCoord;

    function getScrollDiff(event) {
        let delta = 0;

        switch (event.type) {
            case 'mousewheel':
                delta = event.wheelDelta ? event.wheelDelta : -1 * event.deltaY;
                break;
            case 'touchstart':
                startYCoord = event.touches[0].clientY;
                break;
            case 'touchmove': {
                const yCoord = event.touches[0].clientY;

                delta = yCoord - startYCoord;
                startYCoord = yCoord;
                break;
            }
        }

        return delta;
    }

    function getScrollDirection(event) {
        return getScrollDiff(event) >= 0 ? 'UP' : 'DOWN';
    }

    function blockScrollOutside(targetElement, event) {
        const { target } = event;
        const isScrollAllowed = targetElement.contains(target);
        const isTouchStart = event.type === 'touchstart';

        let doScrollBlock = !isTouchStart;

        if (isScrollAllowed) {
            const isScrollingUp = getScrollDirection(event) === 'UP';
            const elementHeight = targetElement.scrollHeight - targetElement.offsetHeight;

            doScrollBlock =
                doScrollBlock &&
                ((isScrollingUp && targetElement.scrollTop <= 0) ||
                    (!isScrollingUp && targetElement.scrollTop >= elementHeight));
        }

        if (doScrollBlock) {
            event.preventDefault();
        }
    }

    return {
        blockScrollOutside,
        getScrollDirection,
    };
}

const scrollManager = ScrollManager();
const testBlock = document.body.querySelector('.test');

function handleScroll(event) {
  scrollManager.blockScrollOutside(testBlock, event);
}

window.addEventListener('scroll', handleScroll);
window.addEventListener('mousewheel', handleScroll);
window.addEventListener('touchstart', handleScroll);
window.addEventListener('touchmove', handleScroll);
.main {
   border: 1px solid red;
   height: 200vh;
 }
 
 .test {
   border: 1px solid green;
   height: 300px;
   width: 300px;
   overflow-y: auto;
   position: absolute;
   top: 100px;
   left: 50%;
 }
 
 .content {
   height: 100vh;
 }
<div class="main">
  <div class="test">
    <div class="content"></div>
  </div>
</div>

于 2018-02-28T16:32:53.373 回答
0

这对我来说适用于 Android 和 IOS 设备。

想象一下,我们有一个div class="backdrop">永远不想滚动的元素。但是我们希望能够滚动到位于 this 之上的元素backdrop

function handleTouchMove(event) {
    const [backdrop] = document.getElementsByClassName('backdrop');
    const isScrollingBackdrop = backdrop === event.target;

    isScrollingBackdrop ? event.preventDefault() : event.stopPropagation();
}

window.addEventListener('touchmove', handleTouchMove, { passive: false });

所以,我们监听touchmove事件,如果我们在背景上滚动,我们会阻止它。如果我们正在滚动其他内容,我们允许它但停止它的传播,因此它也不会滚动backdrop.

当然,这是非常基本的,可以重新设计和扩展很多,但这就是解决我在 VueJs2 项目中的问题的原因。

希望能帮助到你!;)

于 2018-11-08T11:31:26.133 回答
0

我可以通过在 HTML 上添加 css "overflow-y: hidden" 来禁用主文档的滚动。

它根本没有弄乱定位。

于 2019-10-25T03:38:26.717 回答