web-dev-qa-db-fra.com

iOS Safari - Comment désactiver le sur-défilement tout en permettant aux div à défilement de défiler normalement?

Je travaille sur une application Web pour iPad et je dois empêcher le défilement excessif de sorte que cela ressemble moins à une page Web. J'utilise actuellement cela pour geler la fenêtre d'affichage et désactiver le survol:

document.body.addEventListener('touchmove',function(e){
      e.preventDefault();
  });

Cela fonctionne très bien pour désactiver le sur-défilement, mais mon application dispose de plusieurs div à défilement et le code ci-dessus les empêche de défiler .

Je ne vise que iOS 5 et versions ultérieures uniquement, j'ai donc évité les solutions de type hacky, telles que iScroll. Au lieu de cela, j'utilise ce CSS pour mes divs à défilement:

.scrollable {
    -webkit-overflow-scrolling: touch;
    overflow-y:auto;
}

Ceci fonctionne sans le script overscroll de document, mais ne résout pas le problème du défilement div.

Sans un plugin jQuery, existe-t-il un moyen d'utiliser le correctif de défilement mais d'exempter mes divs $ ('. Scrollable') ?

EDIT:

J'ai trouvé quelque chose qui est une solution décente:

 // Disable overscroll / viewport moving on everything but scrollable divs
 $('body').on('touchmove', function (e) {
         if (!$('.scrollable').has($(e.target)).length) e.preventDefault();
 });

La fenêtre d'affichage bouge toujours lorsque vous faites défiler au-delà du début ou de la fin du div. Je voudrais trouver un moyen de désactiver cela aussi.

97
Jeff

Cela résout le problème lorsque vous faites défiler au-delà du début ou de la fin du div

var selScrollable = '.scrollable';
// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});
// Uses body because jQuery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart', selScrollable, 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;
  }
});
// Stops preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove', selScrollable, function(e) {
  e.stopPropagation();
});

Notez que cela ne fonctionnera pas si vous souhaitez bloquer le défilement de la page entière lorsqu'un div n'a pas de débordement. Pour bloquer cela, utilisez le gestionnaire d'événements suivant au lieu de celui immédiatement supérieur (adapté de this question ):

$('body').on('touchmove', selScrollable, function(e) {
    // Only block default if internal div contents are large enough to scroll
    // Warning: scrollHeight support is not universal. (https://stackoverflow.com/a/15033226/40352)
    if($(this)[0].scrollHeight > $(this).innerHeight()) {
        e.stopPropagation();
    }
});
84
Tyler Dodge

Utiliser l'excellent answer de Tyler Dodge est resté à la traîne sur mon iPad, alors j'ai ajouté du code de limitation, il est maintenant assez fluide. Il y a quelques sauts minimes parfois lors du défilement.

// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});

var scrolling = false;

// Uses body because jquery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart','.scrollable',function(e) {

    // Only execute the below code once at a time
    if (!scrolling) {
        scrolling = true;   
        if (e.currentTarget.scrollTop === 0) {
          e.currentTarget.scrollTop = 1;
        } else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight) {
          e.currentTarget.scrollTop -= 1;
        }
        scrolling = false;
    }
});

// Prevents preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove','.scrollable',function(e) {
  e.stopPropagation();
});

De plus, l’ajout du CSS suivant corrige quelques problèmes de rendu ( source ):

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

Commencez par empêcher les actions par défaut sur l'ensemble de votre document comme d'habitude:

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

Puis arrêtez votre classe d'éléments de se propager au niveau du document. Cela l'empêche d'atteindre la fonction ci-dessus et donc e.preventDefault () n'est pas initialisé:

$('.scrollable').bind('touchmove', function(e){
  e.stopPropagation();
});

Ce système semble être plus naturel et moins intensif que le calcul de la classe sur tous les mouvements tactiles. Utilisez .on () plutôt que .bind () pour les éléments générés dynamiquement.

Pensez également à ces balises META pour éviter que des choses malheureuses se produisent lorsque vous utilisez votre div à défilement:

<meta content='True' name='HandheldFriendly' />
<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' name='viewport' />
<meta name="viewport" content="width=device-width" />
12
Jonathan Tonge

Pouvez-vous simplement ajouter un peu plus de logique à votre code de désactivation du sur-défilement pour vous assurer que l'élément ciblé en question n'est pas celui que vous souhaitez faire défiler? Quelque chose comme ça:

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });
7
DeveloperJoe

La meilleure solution à cela est css/html: Créez une div pour envelopper vos éléments, si vous ne l’avez pas déjà. Et réglez-le sur une position fixe et un débordement masqué. Facultatif, définissez la hauteur et la largeur sur 100% si vous souhaitez que tout l'écran soit rempli et que l'écran ne soit complet.

#wrapper{
  height: 100%;
  width: 100%;
  position: fixed;
  overflow: hidden;
}
<div id="wrapper">
  <p>All</p>
  <p>Your</p>
  <p>Elements</p>
</div>
6
Elias Fyksen

Vérifiez si l'élément à faire défiler est déjà défilé vers le haut lorsque vous essayez de faire défiler vers le haut ou vers le bas lorsque vous essayez de faire défiler vers le bas, puis empêchez l'action par défaut d'empêcher toute la page de se déplacer.

var touchStartEvent;
$('.scrollable').on({
    touchstart: function(e) {
        touchStartEvent = e;
    },
    touchmove: function(e) {
        if ((e.originalEvent.pageY > touchStartEvent.originalEvent.pageY && this.scrollTop == 0) ||
            (e.originalEvent.pageY < touchStartEvent.originalEvent.pageY && this.scrollTop + this.offsetHeight >= this.scrollHeight))
            e.preventDefault();
    }
});
5
Johan

Je cherchais un moyen d’empêcher tout défilement du corps lorsqu’il y avait une fenêtre contextuelle avec une zone de défilement (une liste déroulante du "panier" offrant une vue de défilement de votre panier).

J'ai écrit une solution beaucoup plus élégante utilisant du javascript minimal pour basculer simplement la classe "noscroll" sur votre corps lorsque vous avez un menu contextuel ou div que vous souhaitez faire défiler (et non "survoler" le corps entier de la page).

alors que les navigateurs de bureau observent un débordement: masqué - iOS semble ignorer que si vous ne définissez pas la position comme étant fixe ... ce qui donne une largeur étrange à la page, vous devez donc définir la position et la largeur manuellement. utiliser ce css:

.noscroll {
    overflow: hidden;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
}

et ce jquery:

/* fade in/out cart popup, add/remove .noscroll from body */
$('a.cart').click(function() {
    $('nav > ul.cart').fadeToggle(100, 'linear');
    if ($('nav > ul.cart').is(":visible")) {
        $('body').toggleClass('noscroll');
    } else {
        $('body').removeClass('noscroll');
    }
});

/* close all popup menus when you click the page... */
$('body').click(function () {
    $('nav > ul').fadeOut(100, 'linear');
    $('body').removeClass('noscroll');
});

/* ... but prevent clicks in the popup from closing the popup */
$('nav > ul').click(function(event){
    event.stopPropagation();
});
4
nrutas

J'ai un peu de travail sans jQuery. Pas perfert mais fonctionne bien (surtout si vous avez un scroll-x dans un scoll-y) https://github.com/pinadesign/overscroll/

N'hésitez pas à participer et à l'améliorer

3
PiñaDesign

Bien que la désactivation de tous les événements "touchmove" puisse sembler une bonne idée, dès que vous avez besoin d'autres éléments défilables sur la page, cela pose problème. De plus, si vous ne désactivez que les événements "touchmove" sur certains éléments (par exemple body si vous souhaitez que la page ne soit pas défilable), dès qu'elle est activée ailleurs, IOS provoquera une propagation imparable dans Chrome lorsque la barre d’URL bascule.

Bien que je ne puisse pas expliquer ce comportement, il semble que le seul moyen de prévention semble définir la position du corps sur fixed. Le seul problème est que vous perdrez la position du document - ceci est particulièrement gênant dans les modaux par exemple. Une façon de le résoudre serait d'utiliser ces fonctions simples de VanillaJS:

function disableDocumentScrolling() {
    if (document.documentElement.style.position != 'fixed') {
        // Get the top vertical offset.
        var topVerticalOffset = (typeof window.pageYOffset != 'undefined') ?
            window.pageYOffset : (document.documentElement.scrollTop ? 
            document.documentElement.scrollTop : 0);
        // Set the document to fixed position (this is the only way around IOS' overscroll "feature").
        document.documentElement.style.position = 'fixed';
        // Set back the offset position by user negative margin on the fixed document.
        document.documentElement.style.marginTop = '-' + topVerticalOffset + 'px';
    }
}

function enableDocumentScrolling() {
    if (document.documentElement.style.position == 'fixed') {
        // Remove the fixed position on the document.
        document.documentElement.style.position = null;
        // Calculate back the original position of the non-fixed document.
        var scrollPosition = -1 * parseFloat(document.documentElement.style.marginTop);
        // Remove fixed document negative margin.
        document.documentElement.style.marginTop = null;
        // Scroll to the original position of the non-fixed document.
        window.scrollTo(0, scrollPosition);
    }
}

En utilisant cette solution, vous pouvez avoir un document fixe et tout autre élément de votre page peut déborder en utilisant du CSS simple (par exemple, overflow: scroll;). Pas besoin de cours spéciaux ou quoi que ce soit d'autre.

1
Nicolas Bouvrette

Cette solution ne vous oblige pas à mettre une classe à défilement sur tous vos divs à défilement, elle est donc plus générale. Le défilement est autorisé sur tous les éléments qui sont ou sont les enfants des éléments INPUT contenteditables et des fonctions de défilement ou de défilement automatique.

J'utilise un sélecteur personnalisé et je cache également le résultat de la vérification dans l'élément pour améliorer les performances. Pas besoin de vérifier le même élément à chaque fois. Cela peut poser quelques problèmes car ils viennent d'écrire, mais je pense que je les partagerais.

$.expr[':'].scrollable = function(obj) {
    var $el = $(obj);
    var tagName = $el.prop("tagName");
    return (tagName !== 'BODY' && tagName !== 'HTML') && (tagName === 'INPUT' || $el.is("[contentEditable='true']") || $el.css("overflow").match(/auto|scroll/));
};
function preventBodyScroll() {
    function isScrollAllowed($target) {
        if ($target.data("isScrollAllowed") !== undefined) {
            return $target.data("isScrollAllowed");
        }
        var scrollAllowed = $target.closest(":scrollable").length > 0;
        $target.data("isScrollAllowed",scrollAllowed);
        return scrollAllowed;
    }
    $('body').bind('touchmove', function (ev) {
        if (!isScrollAllowed($(ev.target))) {
            ev.preventDefault();
        }
    });
}
1
jcbdrn

celui-ci fonctionne pour moi (javascript simple)

var fixScroll = function (className, border) {  // className = class of scrollElement(s), border: borderTop + borderBottom, due to offsetHeight
var reg = new RegExp(className,"i"); var off = +border + 1;
function _testClass(e) { var o = e.target; while (!reg.test(o.className)) if (!o || o==document) return false; else o = o.parentNode; return o;}
document.ontouchmove  = function(e) { var o = _testClass(e); if (o) { e.stopPropagation(); if (o.scrollTop == 0) { o.scrollTop += 1; e.preventDefault();}}}
document.ontouchstart = function(e) { var o = _testClass(e); if (o && o.scrollHeight >= o.scrollTop + o.offsetHeight - off) o.scrollTop -= off;}
}

fixScroll("fixscroll",2); // assuming I have a 1px border in my DIV

html:

<div class="fixscroll" style="border:1px gray solid">content</div>
0
RJS

Voici une solution compatible zepto

    if (!$(e.target).hasClass('scrollable') && !$(e.target).closest('.scrollable').length > 0) {
       console.log('prevented scroll');
       e.preventDefault();
       window.scroll(0,0);
       return false;
    }
0
Christopher Johnson

Essayez ceci Cela fonctionnera parfaitement.

$('body.overflow-hidden').delegate('#skrollr-body','touchmove',function(e){
    e.preventDefault();
    console.log('Stop skrollrbody');
}).delegate('.mfp-auto-cursor .mfp-content','touchmove',function(e){
    e.stopPropagation();
    console.log('Scroll scroll');
});
0

J'ai eu une chance surprenante avec simple:

body {
    height: 100vh;
}

Cela fonctionne très bien pour désactiver le sur-défilement pour les fenêtres contextuelles ou les menus et il ne force pas les barres du navigateur à ressembler à l'utilisation de position: corrigé. MAIS - vous devez enregistrer la position de défilement avant de définir une hauteur fixe et de la restaurer lorsque vous masquez la fenêtre contextuelle. Dans le cas contraire, le navigateur fera défiler vers le haut.

0
Láďa Durchánek