iOS Safari - Come disabilitare l'overscroll ma consentire ai div scorrevoli di scorrere normalmente?


100

Sto lavorando a un'app Web basata su iPad e devo impedire lo scorrimento eccessivo in modo che sembri meno simile a una pagina Web. Attualmente lo sto usando per bloccare la visualizzazione e disabilitare l'overscroll:

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

Funziona benissimo per disabilitare l'overscroll, ma la mia app ha diversi div scorrevoli e il codice sopra impedisce loro di scorrere .

Sto prendendo di mira solo iOS 5 e versioni successive, quindi ho evitato soluzioni hacker come iScroll. Invece sto usando questo CSS per i miei div scorrevoli:

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

Funziona senza lo script di overscroll del documento, ma non risolve il problema di scorrimento del div.

Senza un plugin jQuery, esiste un modo per utilizzare la correzione di overscroll ma esentare i miei div $ ('. Scrollable')?

MODIFICARE:

Ho trovato qualcosa che è una soluzione decente:

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

Il viewport si sposta ancora quando scorri oltre l'inizio o la fine del div. Mi piacerebbe trovare un modo per disabilitare anche quello.


ho provato anche il tuo ultimo ma non ha funzionato neanche
Santiago Rebella

Sono stato in grado di impedire lo spostamento della visualizzazione quando si scorre oltre la fine del div catturando esplicitamente l'evento di scorrimento sul genitore del div scorrevole e non permettendogli di scorrere effettivamente. Se stai utilizzando jquery mobile, ha senso farlo a livello di pagina in questo modo: $ ('div [data-role = "page"]'). On ('scroll', function (e) {e.preventDefault ();});
Christopher Johnson,


Ho trovato questo script che risolve questo problema! :) github.com/lazd/iNoBounce
Jan Šafránek

Perché pubblicheresti di nuovo il link se qualcuno sopra il tuo post lo avesse pubblicato 7 mesi prima?
Denny

Risposte:


84

Questo risolve il problema quando scorri oltre l'inizio o la fine del 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();
});

Nota che questo non funzionerà se vuoi bloccare lo scorrimento dell'intera pagina quando un div non ha overflow. Per bloccarlo, usa il seguente gestore di eventi invece di quello immediatamente sopra (adattato da questa domanda ):

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

Questo non funzionerà se c'è un iframe all'interno dell'area scorrevole e l'utente inizia a scorrere su quell'iframe. C'è una soluzione alternativa per questo?
Timo

2
Ha funzionato alla grande - questo è decisamente meglio del semplice targeting .scrollablediretto (che è quello che avevo provato originariamente per risolvere questo problema). Se sei un noob JavaScript e desideri un codice facile per rimuovere questi gestori da qualche parte lungo la linea, queste due linee funzionano alla grande per me! $(document).off('touchmove'); AND $('body').off('touchmove touchstart', '.scrollable');
Devin

Ha funzionato perfettamente per me. Grazie mille, mi hai risparmiato ore!
marcgg

1
Questo non funziona se non c'è abbastanza contenuto nel div da scorrere. Qualcuno ha posto una domanda separata che ha risposto a questa domanda: stackoverflow.com/q/16437182/40352
Chris

Come posso consentire più di una classe ".scrollable"? funziona bene con uno ma ho bisogno di rendere scorrevole anche un altro div. Grazie!
MeV

23

L'eccellente risposta di Tyler Dodge continuava a rallentare sul mio iPad, quindi ho aggiunto del codice di limitazione, ora è abbastanza fluido. Ci sono alcuni salti minimi a volte durante lo scorrimento.

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

Inoltre, l'aggiunta del seguente CSS risolve alcuni problemi di rendering ( sorgente ):

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

Questo non funzionerà se c'è un iframe all'interno dell'area scorrevole e l'utente inizia a scorrere su quell'iframe. C'è una soluzione alternativa per questo?
Timo

1
Sembra funzionare perfettamente per il trascinamento all'indietro, ma trascinando verso il basso si sposterà comunque Safari.
Abadaba

1
Una soluzione fantastica ... Grazie mille :)
Aamir Shah

Questo ha funzionato per me. Grazie! Ho impiegato più di 1,5 giorni per risolvere questo problema.
Achintha Samindika

Questo è fantastico, ha funzionato alla grande e mi ha risparmiato ulteriore stress cercando di trovare una soluzione. Grazie Kuba!
Leonard

12

Per prima cosa previeni le azioni predefinite sull'intero documento come al solito:

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

Quindi interrompi la propagazione della classe di elementi a livello di documento. Questo gli impedisce di raggiungere la funzione sopra e quindi e.preventDefault () non viene avviato:

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

Questo sistema sembra essere più naturale e meno intenso del calcolo della classe su tutte le mosse tattili. Usa .on () invece di .bind () per gli elementi generati dinamicamente.

Considera anche questi meta tag per evitare che accadano cose sfortunate durante l'utilizzo del tuo div scorrevole:

<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" />

7

Puoi semplicemente aggiungere un po 'più di logica al tuo codice di disabilitazione overscroll per assicurarti che l'elemento mirato in questione non sia quello che vorresti scorrere? Qualcosa come questo:

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

3
Grazie ... Sembra che dovrebbe funzionare, ma non è così. Inoltre, non dovrebbe essere "scorrevole" e non ".scrollable" (con il punto)?
Jeff

1
Sembra che sia l'elemento più profondamente annidato che riceve l'evento touch, quindi potresti dover controllare tutti i tuoi genitori per vedere se sei in un div scorrevole.
Christopher Johnson,

3
Perché si dovrebbe usare document.body.addEventListener se si usa jQuery? È per una ragione?
fnagel

7

La migliore soluzione a questo è css / html: crea un div in cui avvolgere i tuoi elementi, se non lo hai già e impostalo in posizione fissa e overflow nascosto. Facoltativo, imposta l'altezza e la larghezza al 100% se vuoi che riempia l'intero schermo e nient'altro che l'intero schermo

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


5

Controlla se l'elemento scorrevole è già stato fatto scorrere verso l'alto quando si tenta di scorrere verso l'alto o verso il basso quando si tenta di scorrere verso il basso e quindi si impedisce all'azione predefinita di arrestare lo spostamento dell'intera pagina.

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

Ho dovuto controllare e.originalEvent.touches [0] .pageY invece di e.originalEvent.pageY. Ha funzionato ma solo se sei già alla fine del div di scorrimento. Quando lo scroll è in corso (es. Hai fatto uno scroll molto veloce) non si ferma una volta raggiunta la fine del div scorrevole.
Keen Sage

4

Stavo cercando un modo per impedire lo scorrimento di tutto il corpo quando è presente un popup con un'area scorrevole (un popup "carrello della spesa" che ha una visualizzazione scorrevole del carrello).

Ho scritto una soluzione molto più elegante utilizzando JavaScript minimo per attivare o disattivare la classe "noscroll" sul tuo corpo quando hai un popup o un div che desideri scorrere (e non "overscroll" l'intero corpo della pagina).

mentre i browser desktop osservano overflow: hidden - iOS sembra ignorarlo a meno che non imposti la posizione su fixed ... il che fa sì che l'intera pagina abbia una larghezza strana, quindi devi impostare anche la posizione e la larghezza manualmente. usa questo css:

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

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

Questo è molto utile e un approccio minimo, proprio quello di cui avevo bisogno. Impostazione della posizione su fissa, con cima: 0; sinistra: 0; larghezza: 100%; erano gli elementi che mi mancavano. Ciò è utile anche per i menu a comparsa.
bdanin

3

Ho lavorato un po 'intorno senza jquery. Non perfert ma funziona bene (specialmente se hai uno scroll-x in uno scoll-y) https://github.com/pinadesign/overscroll/

Sentiti libero di partecipare e migliorarlo


1
Ho avuto lo stesso problema di Jeff, ho provato tutte le risposte, la tua ha funzionato. Grazie!
Dominik Schreiber

La risposta accettata ha funzionato solo per me quando il div con .scrollable aveva abbastanza contenuto da causarne l'overflow. Se non traboccava, l'effetto "rimbalzo" esisteva ancora. Comunque funziona perfettamente, grazie!
Adam Marshall

1

Questa soluzione non richiede di mettere una classe scorrevole su tutti i tuoi div scorrevoli, quindi è più generale. Lo scorrimento è consentito su tutti gli elementi che sono o sono figli di INPUT elementi contenteditables e overflow scroll o autos.

Utilizzo un selettore personalizzato e memorizzo anche nella cache il risultato del controllo nell'elemento per migliorare le prestazioni. Non è necessario controllare ogni volta lo stesso elemento. Questo potrebbe avere alcuni problemi come appena scritto, ma ho pensato di condividerlo.

$.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

Anche se disabilitare tutti gli eventi "touchmove" potrebbe sembrare una buona idea, non appena avrai bisogno di altri elementi scorrevoli sulla pagina causerà problemi. Inoltre, se disabiliti solo gli eventi "touchmove" su determinati elementi (ad es. Body se desideri che la pagina non sia scorrevole), non appena viene abilitato altrove, IOS causerà una propagazione inarrestabile in Chrome quando l'URL barra alterna.

Anche se non posso spiegare questo comportamento, sembra che l'unico modo per prevenire sembra impostare la posizione del corpo su fixed. L'unico problema è che perderai la posizione del documento - questo è particolarmente fastidioso, ad esempio, nei modali. Un modo per risolverlo sarebbe utilizzare queste semplici funzioni di 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);
    }
}

Usando questa soluzione puoi avere un documento fisso e qualsiasi altro elemento nella tua pagina può traboccare usando semplici CSS (ad esempio overflow: scroll;). Non c'è bisogno di lezioni speciali o altro.


0

Ecco una soluzione compatibile con 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

questo funziona per me (semplice javascript)

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

Prova questo funzionerà perfettamente.

$('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

Ho avuto una fortuna sorprendente con semplici:

body {
    height: 100vh;
}

Funziona benissimo per disabilitare l'overscroll per pop-up o menu e non forza la visualizzazione delle barre del browser come quando si usa position: fixed. MA - è necessario salvare la posizione di scorrimento prima di impostare l'altezza fissa e ripristinarla quando si nasconde il popup, altrimenti il ​​browser scorrerà verso l'alto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.