iOS Safari: ¿Cómo deshabilitar el desplazamiento excesivo pero permitir que los divs desplazables se desplacen normalmente?

100

Estoy trabajando en una aplicación web basada en iPad y necesito evitar el desplazamiento excesivo para que parezca menos una página web. Actualmente estoy usando esto para congelar la ventana gráfica y deshabilitar el desplazamiento excesivo:

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

Esto funciona muy bien para deshabilitar el desplazamiento excesivo, pero mi aplicación tiene varios divs desplazables y el código anterior evita que se desplacen .

Solo apunto a iOS 5 y superior, así que he evitado soluciones piratas como iScroll. En su lugar, estoy usando este CSS para mis divs desplazables:

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

Esto funciona sin el script de desplazamiento excesivo del documento, pero no resuelve el problema de desplazamiento div.

Sin un complemento de jQuery, ¿hay alguna forma de usar la corrección de desplazamiento excesivo pero eximir mis divs $ ('. Scrollable')?

EDITAR:

Encontré algo que es una solución decente:

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

La ventana gráfica aún se mueve cuando se desplaza más allá del principio o el final del div. Me gustaría encontrar una manera de desactivar eso también.

Jeff
fuente
probé el último también, pero tampoco funcionó
Santiago Rebella
Pude evitar que la ventana gráfica se moviera cuando se desplaza más allá del final del div al capturar explícitamente el evento de desplazamiento en el elemento principal del div desplazable y no permitir que se desplace realmente. Si está utilizando jquery mobile, tiene sentido hacer esto a nivel de página así: $ ('div [data-role = "page"]'). On ('scroll', function (e) {e.preventDefault ();});
Christopher Johnson
github.com/lazd/iNoBounce hace maravillas
David Underhill
¡He encontrado este script que soluciona este problema! :) github.com/lazd/iNoBounce
Jan Šafránek
¿Por qué volvería a publicar el enlace si alguien que está encima de su publicación lo publicó 7 meses antes?
Denny

Respuestas:

84

Esto resuelve el problema cuando se desplaza más allá del principio o final 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();
});

Tenga en cuenta que esto no funcionará si desea bloquear el desplazamiento de toda la página cuando un div no tiene desbordamiento. Para bloquear eso, use el siguiente controlador de eventos en lugar del inmediatamente anterior (adaptado de esta pregunta ):

$('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();
    }
});
Tyler Dodge
fuente
Esto no funcionará si hay un iframe dentro del área desplazable y el usuario comienza a desplazarse en ese iframe. ¿Hay una solución para esto?
Timo
2
Funcionó muy bien, esto es definitivamente mejor que solo apuntar .scrollabledirectamente (que es lo que había intentado originalmente para resolver este problema). Si eres un novato de JavaScript y quieres un código fácil para eliminar estos controladores en algún momento, ¡estas dos líneas funcionan muy bien para mí! $(document).off('touchmove'); Y $('body').off('touchmove touchstart', '.scrollable');
Devin
Funcionó perfectamente para mí. ¡Muchas gracias, me ahorraste horas!
marcgg
1
Esto no funciona si no hay suficiente contenido en el div para desplazarse. Alguien hizo una pregunta separada que respondió eso aquí: stackoverflow.com/q/16437182/40352
Chris
¿Cómo puedo permitir más de una clase ".scrollable"? funciona bien con uno, pero también necesito hacer otro div desplazable. ¡Gracias!
MeV
23

El uso de la excelente respuesta de Tyler Dodge se mantuvo rezagado en mi iPad, así que agregué un código de aceleración, ahora es bastante suave. A veces hay algunos saltos mínimos durante el desplazamiento.

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

Además, agregar el siguiente CSS corrige algunos fallos de renderizado ( fuente ):

.scrollable {
    overflow: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}
.scrollable * {
    -webkit-transform: translate3d(0,0,0);
}
Kuba Holuj
fuente
Esto no funcionará si hay un iframe dentro del área desplazable y el usuario comienza a desplazarse en ese iframe. ¿Hay una solución para esto?
Timo
1
Parece funcionar perfecto para arrastrar hacia atrás, pero arrastrar hacia abajo seguirá moviendo safari.
Abadaba
1
Una solución increíble ... Muchas gracias :)
Aamir Shah
Esto funcionó para mí. ¡Gracias! Dedico más de 1,5 días a solucionar este problema.
Achintha Samindika
Esto es increíble, funcionó muy bien y me ahorró más estrés al tratar de encontrar una solución. ¡Gracias Kuba!
Leonard
12

Primero, evite las acciones predeterminadas en todo su documento como de costumbre:

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

Luego, evita que tu clase de elementos se propague al nivel del documento. Esto evita que alcance la función anterior y, por lo tanto, e.preventDefault () no se inicia:

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

Este sistema parece ser más natural y menos intensivo que calcular la clase en todos los movimientos táctiles. Utilice .on () en lugar de .bind () para elementos generados dinámicamente.

También considere estas metaetiquetas para evitar que sucedan cosas desafortunadas mientras usa su div desplazable:

<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" />
Jonathan Tonge
fuente
7

¿Puede simplemente agregar un poco más de lógica en su código de deshabilitación de desplazamiento para asegurarse de que el elemento objetivo en cuestión no sea uno que le gustaría desplazar? Algo como esto:

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });
Desarrollador Joe
fuente
3
Gracias ... Parece que esto debería funcionar, pero no es así. Además, ¿no debería ser "desplazable" y no ". Desplazable" (con el punto)?
Jeff
1
Parece que es el elemento más profundamente anidado que recibe el evento táctil, por lo que es posible que deba verificar a todos sus padres para ver si se encuentra en un div desplazable.
Christopher Johnson
3
¿Por qué se usaría document.body.addEventListener si se usa jQuery? ¿Eso es por alguna razón?
fnagel
7

La mejor solución para esto es css / html: cree un div para envolver sus elementos, si aún no lo tiene y configúrelo en la posición fija y desbordamiento oculto. Opcional, establezca la altura y el ancho al 100% si desea que ocupe toda la pantalla y nada más que toda la pantalla

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

Elias Fyksen
fuente
5

Compruebe si el elemento desplazable ya se ha desplazado hacia la parte superior cuando intenta desplazarse hacia arriba o hacia abajo cuando intenta desplazarse hacia abajo y luego evita que la acción predeterminada detenga el movimiento de toda la página.

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();
    }
});
Johan
fuente
Tuve que buscar e.originalEvent.touches [0] .pageY en lugar de e.originalEvent.pageY. Funcionó, pero solo si ya está al final del div de desplazamiento. Cuando el desplazamiento está en progreso (por ejemplo, se ha desplazado muy rápido) no se detiene una vez que se alcanza el final del div desplazable.
Keen Sage
4

Estaba buscando una manera de evitar que todo el cuerpo se desplace cuando hay una ventana emergente con un área desplazable (una ventana emergente de "carrito de compras" que tiene una vista desplazable de su carrito).

Escribí una solución mucho más elegante usando javascript mínimo para simplemente alternar la clase "noscroll" en tu cuerpo cuando tienes una ventana emergente o div que te gustaría desplazar (y no "desplazar demasiado" todo el cuerpo de la página).

mientras que los navegadores de escritorio observan desbordamiento: oculto - iOS parece ignorar eso a menos que establezca la posición en fija ... lo que hace que toda la página tenga un ancho extraño, por lo que también debe establecer la posición y el ancho manualmente. usa este CSS:

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

y este 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();
});
nrutas
fuente
Esto es muy útil y un enfoque mínimo, justo lo que necesitaba. Establecer la posición en fijo, con top: 0; izquierda: 0; ancho: 100%; eran los elementos que me faltaban. Esto también es útil para menús desplegables.
bdanin
3

He trabajado un poco sin jquery. No es perfecto, pero funciona bien (especialmente si tiene un scroll-x en un scoll-y) https://github.com/pinadesign/overscroll/

Siéntete libre de participar y mejorarlo

PiñaDiseño
fuente
1
Tuve el mismo problema que Jeff, probé todas las respuestas, la tuya funcionó. ¡Gracias!
Dominik Schreiber
La respuesta aceptada solo funcionó para mí cuando el div con .scrollable tenía suficiente contenido para hacer que se desbordara. Si no se desbordaba, el efecto de "rebote" aún existía. Sin embargo, esto funciona perfectamente, ¡gracias!
Adam Marshall
1

Esta solución no requiere que coloques una clase desplazable en todos tus divs desplazables, por lo que es más general. Se permite el desplazamiento en todos los elementos que son, o son hijos de, elementos INPUT contenteditables y overflow scroll o autos.

Utilizo un selector personalizado y también guardo en caché el resultado de la verificación en el elemento para mejorar el rendimiento. No es necesario comprobar el mismo elemento cada vez. Esto puede tener algunos problemas, ya que se acaba de escribir, pero pensé en compartirlo.

$.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();
        }
    });
}
jcbdrn
fuente
1

Aunque deshabilitar todos los eventos "touchmove" puede parecer una buena idea, tan pronto como necesite otros elementos desplazables en la página, esto causará problemas. Además de eso, si solo deshabilita los eventos "touchmove" en ciertos elementos (por ejemplo, el cuerpo si desea que la página no sea desplazable), tan pronto como se habilite en cualquier otro lugar, IOS causará una propagación imparable en Chrome cuando la URL barra alterna.

Si bien no puedo explicar este comportamiento, parece que la única forma de prevenir parece establecer la posición del cuerpo en fixed. El único problema es que perderá la posición del documento; esto es especialmente molesto en los modales, por ejemplo. Una forma de resolverlo sería usar estas funciones 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);
    }
}

Con esta solución, puede tener un documento fijo y cualquier otro elemento en su página puede desbordarse usando CSS simple (por ejemplo, overflow: scroll;). No hay necesidad de clases especiales ni nada más.

Nicolas Bouvrette
fuente
0

Aquí hay una solución compatible 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;
    }
Christopher Johnson
fuente
0

este funciona para mí (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>
RJS
fuente
0

Prueba esto Funcionará perfecto.

$('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');
});
Srinivasan Thirupathi
fuente
0

Tuve una suerte sorprendente con lo simple:

body {
    height: 100vh;
}

Funciona muy bien para deshabilitar el desplazamiento excesivo para ventanas emergentes o menús y no obliga a las barras del navegador a aparecer como cuando se usa la posición: fija. PERO: debe guardar la posición de desplazamiento antes de establecer la altura fija y restaurarla cuando se oculta la ventana emergente; de ​​lo contrario, el navegador se desplazará hacia la parte superior.

Láďa Durchánek
fuente