web-dev-qa-db-fra.com

Comment désactiver l'élastique dans les applications Web iOS?

Ce:

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

Fonctionne, mais désactivera le défilement sur toute la page, ce qui est loin d’être idéal.

Ce:

$('*').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;
});

Fonctionne sur les pages comportant une zone de défilement. Cependant, quand il n'y a rien à faire défiler, il affichera à nouveau l'élastique.

Donc ma question:

Comment désactiver l'effet d'élastique tout en conservant les zones -webkit-overflow-scrolling défilables?

[Mettre à jour]

Meilleure solution

Désactivez le défilement sur tous les éléments non défilables tels qu'une barre de tabulation ou une barre de navigation.

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

Associez un gestionnaire de défilement aux éléments défilables tels que le contenu principal.

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

Récemment, je me suis retrouvé dans le même problème avec un SPA où le <body> caoutchouc-banding nuisait à l'expérience, mais j'avais besoin de faire défiler les sous-zones. Un grand merci aux suggestions de dSquared, car la méthode 1 me convenait le mieux. Voici ma petite extension de sa suggestion que j'ai implémentée dans un projet de travail qui regarde tout le long de l'arbre pour trouver tous les éléments (pas seulement les divs) qui ont une classe .scroll:

// 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();
    }
});

Et voici à quoi ressemble le 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; }

Vous n'avez besoin que d'une seule bibliothèque (jQuery ou Zepto ) et vous obtenez un défilement natif avec élan et sans élastiques sur le corps. De plus, j'ai ajouté le translateZ pour résoudre certains problèmes de disparition d'éléments lors du défilement et il peut être utilisé pour GPU accélérer vos éléments

MAIS (et c’est un gros mais), comme le souligne dSquared, l’ensemble de la page devient élastique lorsque l’élément de défilement est à sa limite et tente de faire défiler davantage. Personnellement, j’estime que c’est un échec, je continue donc à y travailler. Je voulais juste essayer de comprendre. Ajouter une vérification le long du code de l'OP pourrait être la solution, mais je ne l'ai pas essayée.

MISE À JOUR (10/7/12):

Après beaucoup de travail, le code suivant fonctionne parfaitement dans iOS6 (je n'ai testé rien d'autre). Aucune bande de caoutchouc sur le corps, plus de problèmes à la limite de la zone de défilement et performances de défilement natives. C'est évidemment beaucoup plus de code qu'à l'origine, mais je pense que cela donnera le comportement le plus proche des objectifs du PO. 

(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);
30
Tim Hall

Malheureusement, il n’existe pas de solution miracle à ce problème, car le défilement élastique sur Mobile Safari est une «fonctionnalité» intégrée du navigateur lui-même. En utilisant un mécanisme de défilement par défaut fourni par le navigateur, vous obtiendrez un certain défilement du ruban élastique.

Je suggérerais deux façons de résoudre ce problème:

Méthode 1

Liez-vous à l'événement touchmove sur l'élément </body> et vérifiez la cible de l'événement touchmove pour voir si vous souhaitez qu'il se déclenche ou non de la manière suivante:

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

Exemple JSFiddle ici

Cette méthode utilise le défilement par défaut du navigateur. Toutefois, elle présente l'inconvénient de laisser le défilement élastique en haut ou en bas du code de défilement </div>.

Méthode 2

Lier à l'événement touchmove de l'élément </body> comme auparavant, mais dans ce cas, nous empêchons les événements Toustouchmove et nous nous appuyons sur l'excellent iScroll 4 plugin pour gérer le défilement, comme suit:

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

Exemple JSFiddle ici

Ceci est ma méthode préférée car il bloque tout défilement élastique et fournit une zone de défilement agréable, mais il repose sur l’utilisation d’un plugin.

J'espère que ça aide

8
dSquared

Voici une solution qui utilise jQuery et Hammer.js (jquery-implementation). Cela fait deux bibliothèques, mais si vous travaillez sur mobile, il est probable que vous voudrez quand même inclure Hammer.

Pour chaque événement glisser vers le haut (de sorte que les interactions glisser non-défilantes puissent utiliser stopPropagation), le gestionnaire vérifie s'il bouillonne dans les éléments avec class = scrolling, si oui, si l'utilisateur fait défiler dans les limites autorisées. scrollContainer et seulement alors autorise-t-il le défilement natif.

$("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();
    }
});

Pour tuer des dragées par pincement, etc. échappe si nécessaire pour autoriser le zoom si votre vue est évolutive

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

Testé sur ios7/safari, Android4.3/webview et Android4.3/firefoxMobile25 et la seule solution qui ne s'est pas cassée.

2
Tobl

Sur la base de la réponse de @ Mark, nous avons proposé cette alternative, qui semble fonctionner. Remplacez .page_list par les noms de classe des éléments pouvant défiler.

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();
        }    
    });
}
1
Crashalot

J'ai écrit, à mon avis, la meilleure solution à ce problème. Cela désactivera le défilement en général, sauf si l'élément y a défilement.

/********************************************************************************
 * 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();
    });
});
1
Mark

Enfin, j'ai mélangé quelques méthodes et ces codes sont la version de travail . Mais vous devez inclure le 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();
});
0
ozgrozer