如何禁用iOS网络应用中的橡皮筋?

时间:2012-04-27 21:06:18

标签: jquery web-applications rubber-band

此:

$('body').on('touchmove', function(e) { e.preventDefault(); });

可以使用,但会禁用整个页面的滚动,这远非理想。

此:

$('*').on('touchstart', function(e){
    var element = $(this).get(0);

    if ( element.scrollTop <= 0 )                                           element.scrollTop = 1;
    if ( element.scrollTop + element.offsetHeight >= element.scrollHeight ) element.scrollTop = element.scrollHeight - element.offsetHeight - 1;
});

适用于具有滚动区域的网页。然而,当没有任何东西可以滚动时,它将再次显示橡皮筋。

所以我的问题:

如何禁用橡皮筋效果并仍保持-webkit-overflow-scrolling区域可滚动?

[更新]

最佳解决方案

禁用所有不可滚动元素(如标签栏或导航栏)的滚动。

anElement.addEventListener('touchmove', function( event ){ event.preventDefault() };

将滚动处理程序附加到可滚动元素,例如主要内容。

anElement.addEventListener('touchstart', function( event ){
        if( this.scrollTop === 0 ) {
            this.scrollTop += 1;
        } else if( this.scrollTop + this.offsetHeight >= this.scrollHeight ) {
            this.scrollTop -= 1;
        }
}

8 个答案:

答案 0 :(得分:30)

最近出现了同样的问题,其中<body>橡皮筋正在减损体验,但我需要在子区域滚动。非常感谢dSquared的建议,因为方法1最适合我。这是我对他的建议的小扩展,我在一个工作项目中实现,它在树上一直向上找到任何有.scroll类的元素(不仅仅是div):

// Prevent rubber-banding of the body, but allow for scrolling elements
$('body').on('touchmove', function (e) {
    var searchTerms = '.scroll, .scroll-y, .scroll-x',
        $target = $(e.target),
        parents = $target.parents(searchTerms);

    if (parents.length || $target.hasClass(searchTerms)) {
        // ignore as we want the scroll to happen
        // (This is where we may need to check if at limit)
    } else {
        e.preventDefault();
    }
});

以下是CSS的样子:

body {
    height: 100%;
    overflow: hidden;
}
.scroll, .scroll-y, .scroll-x {
    -webkit-overflow-scrolling: touch;
}
.scroll > *, .scroll-y > *, .scroll-x > * {
    -webkit-transform : translateZ(0);
}
.scroll { overflow: auto; }
.scroll-y { overflow-y: auto; }
.scroll-x { overflow-x: auto; }

你只需要一个库(jQuery或Zepto),你就可以获得动态的原生滚动,身体上没有橡皮筋。另外,我添加了translateZ来解决在滚动过程中元素消失时遇到的一些问题,它可以用于GPU accelerate your elements

BUT(这是一个很大的但是),正如dSquared所指出的那样,当滚动元素处于极限并试图进一步滚动时整个页面橡皮筋。就个人而言,我认为这是一个失败,所以我继续努力,只是想插手试图解决这个问题。在OP代码的行上添加一个检查可能就是答案,但我还没有尝试过。

更新(10/7/12):

经过大量的工作,我已经在iOS6中完美地运行了以下代码(尚未测试任何其他内容)。机身上没有橡皮筋,在滚动区域的极限处没有更多问题,并且它始终具有原生滚动性能。显然,最初的代码要多得多,但我认为这会使行为最接近OP的目标。

(function registerScrolling($) {
    var prevTouchPosition = {},
        scrollYClass = 'scroll-y',
        scrollXClass = 'scroll-x',
        searchTerms = '.' + scrollYClass + ', .' + scrollXClass;

    $('body').on('touchstart', function (e) {
        var $scroll = $(e.target).closest(searchTerms),
            targetTouch = e.originalEvent.targetTouches[0];

        // Store previous touch position if within a scroll element
        prevTouchPosition = $scroll.length ? { x: targetTouch.pageX, y: targetTouch.pageY } : {};
    });

$('body').on('touchmove', function (e) {
    var $scroll = $(e.target).closest(searchTerms),
        targetTouch = e.originalEvent.targetTouches[0];

    if (prevTouchPosition && $scroll.length) {
        // Set move helper and update previous touch position
        var move = {
            x: targetTouch.pageX - prevTouchPosition.x,
            y: targetTouch.pageY - prevTouchPosition.y
        };
        prevTouchPosition = { x: targetTouch.pageX, y: targetTouch.pageY };

        // Check for scroll-y or scroll-x classes
        if ($scroll.hasClass(scrollYClass)) {
            var scrollHeight = $scroll[0].scrollHeight,
                outerHeight = $scroll.outerHeight(),

                atUpperLimit = ($scroll.scrollTop() === 0),
                atLowerLimit = (scrollHeight - $scroll.scrollTop() === outerHeight);

            if (scrollHeight > outerHeight) {
                // If at either limit move 1px away to allow normal scroll behavior on future moves,
                // but stop propagation on this move to remove limit behavior bubbling up to body
                if (move.y > 0 && atUpperLimit) {
                    $scroll.scrollTop(1);
                    e.stopPropagation();
                } else if (move.y < 0 && atLowerLimit) {
                    $scroll.scrollTop($scroll.scrollTop() - 1);
                    e.stopPropagation();
                }

                // If only moving right or left, prevent bad scroll.
                if(Math.abs(move.x) > 0 && Math.abs(move.y) < 3){
                  e.preventDefault()
                }

                // Normal scrolling behavior passes through
            } else {
                // No scrolling / adjustment when there is nothing to scroll
                e.preventDefault();
            }
        } else if ($scroll.hasClass(scrollXClass)) {
            var scrollWidth = $scroll[0].scrollWidth,
                outerWidth = $scroll.outerWidth(),

                atLeftLimit = $scroll.scrollLeft() === 0,
                atRightLimit = scrollWidth - $scroll.scrollLeft() === outerWidth;

            if (scrollWidth > outerWidth) {
                if (move.x > 0 && atLeftLimit) {
                    $scroll.scrollLeft(1);
                    e.stopPropagation();
                } else if (move.x < 0 && atRightLimit) {
                    $scroll.scrollLeft($scroll.scrollLeft() - 1);
                    e.stopPropagation();
                }
                // If only moving up or down, prevent bad scroll.
                if(Math.abs(move.y) > 0 && Math.abs(move.x) < 3){
                  e.preventDefault();
                }

                // Normal scrolling behavior passes through
            } else {
                // No scrolling / adjustment when there is nothing to scroll
                e.preventDefault();
            }
        }
    } else {
        // Prevent scrolling on non-scrolling elements
        e.preventDefault();
    }
});
})(jQuery);

答案 1 :(得分:8)

不幸的是,由于Mobile Safari上的橡皮筋滚动是浏览器本身的内置“功能”,因此没有“神奇的子弹”修复。通过使用浏览器提供的任何默认滚动机制,您将最终在某种程度上进行橡皮筋滚动。

我建议有两种解决方法:

方法1

绑定到touchmove元素上的</body>事件并检查touchmove事件的目标,看看是否要触发它:

HTML

<div class="scroll">
    <p>...</p>
    <p>...</p>
</div>

JS

$('body').on('touchmove', function(e) {
    // this is the node the touchmove event fired on
    // in this example it would be the </p> element
    target = e.target;

    // we need to find the parent container
    // we get it like so; assumes div as parent
    parent = $(e.target).closest('div');

    // check if the parent is a scroll window by class //
    if ($(parent).hasClass('scroll')){
        // ignore as we want the scroll to happen
    } else {
        e.preventDefault();
    }
});

<强> JSFiddle Example Here

此方法使用浏览器的默认滚动,但它的缺点是,在滚动页面</div>的顶部或底部仍然会滚动橡皮筋。

方法2

与以前一样绑定到touchmove元素的</body>事件,但在这种情况下,我们会阻止所有 touchmove事件,并依赖于优秀的{{ 3}}插件来处理滚动,如下所示:

HTML

<div id="wrapper">
    <div id="scroller">
        <p>...</p>
        <p>...</p>
    </div>
</div>

JS

$(document).ready(function(){
    // prevent all scroll //
    $('body').on('touchmove', function(e) {
        e.preventDefault();
    });

    // apply iscroll to scrolling element
    // requires use of id
    var newscroll = new iScroll('wrapper');
});​​

<强> iScroll 4

这是我首选的方法,因为它会阻止所有橡皮筋滚动并提供一个漂亮的滚动区域,但它依赖于使用插件。

我希望这会有所帮助

答案 2 :(得分:2)

这是一个使用jQuery和Hammer.js(jquery-implementation)的解决方案。那是两个图书馆,但是如果你正在使用移动设备,那么你很可能还想要包括Hammer。

对于每个冒泡到顶部的拖拽事件(因此非滚动拖拽 - 交互可以使用stopPropagation),处理程序检查它是否通过class = scrolling鼓泡通过任何元素,如果是,则用户是否在允许的范围内滚动该scrollContainer的边界,然后才允许本地滚动。

$("body").hammer().on('drag swipe', function(e){

    var scrollTarget = $(e.gesture.target).closest(".scrollable");
    if(scrollTarget.length)
    {
        var scrollTopMax = scrollTarget[0].scrollHeight - scrollTarget.outerHeight();
        if(scrollTopMax > 0){
            var scrollTop = scrollTarget.scrollTop();
            if(scrollTop > 0 && scrollTop < scrollTopMax){
                //console.log("scrolling in the middle");
            }
            else if(scrollTop <= 0 && e.gesture.deltaY < 0){
                //console.log("scrolling from top");
            }
            else if(scrollTop >= scrollTopMax && e.gesture.deltaY > 0){
                //console.log("scrolling from bottom");
            }
            else{
                //console.log("trying to scroll out of boundaries");
                e.gesture.preventDefault();
            }
        }
        else{
            //console.log("content to short to scroll");
            e.gesture.preventDefault();
        }
    }
    else{
        //console.log("no containing element with class=scrollable");
        e.gesture.preventDefault();
    }
});

通过捏等杀死拖拽;如果您的视图是用户可扩展的,则根据需要进行转义以允许缩放

$("body").hammer().on('doubletap rotate pinch', function(e){
    e.gesture.preventDefault();
});

测试了ios7 / safari,android4.3 / webview和android4.3 / firefoxMobile25以及唯一没有破解的解决方案。

答案 3 :(得分:1)

在我看来,我写了这个问题的最佳解决方案。它将禁用一般滚动,除非元素有y滚动。

/********************************************************************************
 * Disable rubber band (c)2013 - Mark van Wijnen | www.CrystalMinds.nl
 ********************************************************************************/
$(function(){
    var scrollY = 0;

    $(document).on('touchstart', function( e ){
        scrollY = e.originalEvent.touches.item(0).clientY;
    });

    $(document).on('touchmove', function( e ){
        var scrollPos       = e.target.scrollTop;
        var scrollDelta     = scrollY - e.originalEvent.touches.item(0).clientY;
        var scrollBottom    = scrollPos + $(e.target).height();
        scrollY             = e.originalEvent.touches.item(0).clientY;

        if ( $(e.target).css( 'overflow-y' ) != 'scroll' || ( scrollDelta < 0 && scrollPos == 0 ) || ( scrollDelta > 0 && scrollBottom == e.target.scrollHeight ) ) 
            e.preventDefault();
    });
});

答案 4 :(得分:1)

根据@Mark的回答,我们想出了这个替代方案,这似乎有效。将.page_list替换为可滚动项的类名。

var INITIAL_Y = 0; // Tracks initial Y position, needed to kill Safari bounce effect

function kill_safari_bounce() {
    $( document ).on( 'touchstart', function( e ){
        INITIAL_Y = e.originalEvent.touches[0].clientY;
    });

    $( document ).on( 'touchmove', function( e ) {
        // Get scrollable ancestor if one exists
        var scrollable_ancestor = $( e.target ).closest( '.page_list' )[0];

        // Nothing scrollable? Block move.
        if ( !scrollable_ancestor ) {
            e.preventDefault();
            return;
        }

        // If here, prevent move if at scrollable boundaries.
        var scroll_delta = INITIAL_Y - e.originalEvent.touches[0].clientY;
        var scroll_pos = scrollable_ancestor.scrollTop;         
        var at_bottom = (scroll_pos + $(scrollable_ancestor).height()) == scrollable_ancestor.scrollHeight;

        if ( (scroll_delta < 0 && scroll_pos == 0) ||
             (scroll_delta > 0 && at_bottom) ){
            e.preventDefault();
        }    
    });
}

答案 5 :(得分:0)

最后我混合了一些方法,这些代码是工作版本。 但您必须包含hammer.js

CSS

.scrollable{
    overflow:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;
    *{-webkit-transform:translate3d(0,0,0);}
}

JAVASCRIPT

$(document).on("touchmove",function(e){
    e.preventDefault();
});
$("body").on("touchstart",".scrollable",function(e){
    if(e.currentTarget.scrollTop===0){
        e.currentTarget.scrollTop = 1;
    }else if(e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight){
        e.currentTarget.scrollTop-=1;
    }
});
$("body").on("touchmove",".scrollable",function(e){
    e.stopPropagation();
});

$("body").hammer().on("pinch",function(e){
    e.gesture.preventDefault();
});

答案 6 :(得分:0)

有人曾经考虑只使用固定在身体上的姿势吗? 那是一个不错的,简单的本地解决方案。不需要Javascript。

body{
    position: fixed;
}

答案 7 :(得分:-1)

只需在想要防止弹跳的div中添加-webkit-overflow-scrolling: auto;