iOS Safari - Aşırı kaydırma nasıl devre dışı bırakılır, ancak kaydırılabilir div'lerin normal şekilde kaydırılmasına izin verilir?


100

İPad tabanlı bir web uygulaması üzerinde çalışıyorum ve bir web sayfasına daha az benzemesi için aşırı kaydırmayı önlemem gerekiyor. Şu anda bunu görüntü alanını dondurmak ve fazla kaydırmayı devre dışı bırakmak için kullanıyorum:

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

Bu, fazla kaydırmayı devre dışı bırakmak için harika çalışıyor, ancak uygulamamın birkaç kaydırılabilir div'i var ve yukarıdaki kod bunların kaydırılmasını engelliyor .

Yalnızca iOS 5 ve üzerini hedefliyorum, bu nedenle iScroll gibi bilgisayar korsanlığı çözümlerinden kaçındım. Bunun yerine, kaydırılabilir div'lerim için şu CSS'yi kullanıyorum:

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

Bu, belge fazla kaydırma komut dosyası olmadan çalışır, ancak div kaydırma sorununu çözmez.

Bir jQuery eklentisi olmadan , fazla kaydırma düzeltmesini kullanıp $ ('. Scrollable') div'lerimi muaf tutmanın bir yolu var mı?

DÜZENLE:

İyi bir çözüm olan bir şey buldum:

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

Görüntü alanı, div'in başını veya sonunu kaydırdığınızda hareket etmeye devam eder. Bunu da devre dışı bırakmanın bir yolunu bulmak istiyorum.


sonuncusunu da denedi ama işe yaramadı
Santiago Rebella

Kaydırılabilir div öğesinin üst öğesinde kaydırma olayını açıkça yakalayarak ve gerçekte kaydırılmasına izin vermeyerek, div'in sonunu kaydırdığınızda görüntü kapısının hareket etmesini önledim. Jquery mobile kullanıyorsanız, bunu sayfa düzeyinde şu şekilde yapmak mantıklıdır: $ ('div [data-role = "page"]'). On ('scroll', function (e) {e.preventDefault ();});
Christopher Johnson


Bu sorunu çözen bu betiği buldum! :) github.com/lazd/iNoBounce
Oca Šafránek

Gönderinizin üstünde biri 7 ay önce göndermişse, bağlantıyı neden tekrar gönderiyorsunuz?
Denny

Yanıtlar:


84

Bu, div'in başını veya sonunu kaydırdığınızda sorunu çözer

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

Bir div taşması olmadığında tüm sayfanın kaydırılmasını engellemek istiyorsanız bunun işe yaramayacağını unutmayın. Bunu engellemek için, hemen yukarıdaki olay yerine aşağıdaki olay işleyiciyi kullanın ( bu sorudan uyarlanmıştır ):

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

Kaydırılabilir alanın içinde bir iframe varsa ve kullanıcı bu iframe üzerinde kaydırma yapmaya başlarsa bu işe yaramaz. Bunun için bir çözüm var mı?
Timo

2
Harika çalıştı - bu kesinlikle .scrollabledoğrudan hedeflemekten daha iyi (bu sorunu çözmek için ilk başta denediğim şey buydu). JavaScript noob iseniz ve bu işleyicileri hattın aşağısında kaldırmak için kolay bir kod istiyorsanız, bu iki satır benim için harika çalışıyor! $(document).off('touchmove'); AND $('body').off('touchmove touchstart', '.scrollable');
Devin

Benim için mükemmel çalıştı. Çok teşekkürler, bana saatler kazandırdın!
marcgg

1
Div'de kaydırmak için yeterli içerik yoksa bu işe yaramaz. Birisi burada bunu yanıtlayan ayrı bir soru yaptı: stackoverflow.com/q/16437182/40352
Chris

Birden fazla ".scrollable" sınıfa nasıl izin verebilirim? biriyle iyi çalışıyor ancak kaydırılabilir başka bir div yapmam gerekiyor. Teşekkürler!
MeV

23

Tyler Dodge'un mükemmel cevabını kullanmak iPad'imde gecikmeye devam etti, bu yüzden biraz azaltma kodu ekledim, şimdi oldukça pürüzsüz. Kaydırma sırasında bazen minimum atlama olur.

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

Ayrıca, aşağıdaki CSS'nin eklenmesi bazı oluşturma hatalarını ( kaynak ) düzeltir :

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

Kaydırılabilir alanın içinde bir iframe varsa ve kullanıcı bu iframe üzerinde kaydırma yapmaya başlarsa bu işe yaramaz. Bunun için bir çözüm var mı?
Timo

1
Geriye doğru sürüklemek için mükemmel görünüyor, ancak aşağı doğru sürüklemek yine de safariyi hareket ettirecek.
Abadaba

1
Harika bir çözüm ... Çok teşekkürler :)
Aamir Shah

Bu benim için çalıştı. Teşekkürler! Bu sorunu çözmek için 1,5 günden fazla zaman harcıyorum.
Achintha Samindika

Bu harika, harika çalıştı ve bir çözüm bulmaya çalışırken beni daha fazla strese soktu. Teşekkürler Kuba!
Leonard

12

Öncelikle, her zamanki gibi belgenizin tamamında varsayılan eylemleri önleyin:

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

Ardından, öğe sınıfınızın belge düzeyine yayılmasını durdurun. Bu, yukarıdaki işleve ulaşmasını durdurur ve bu nedenle e.preventDefault () başlatılmaz:

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

Bu sistem, tüm dokunma hareketlerinde sınıfı hesaplamaktan daha doğal ve daha az yoğun görünüyor. Dinamik olarak oluşturulan öğeler için .bind () yerine .on () kullanın.

Kaydırılabilir div'inizi kullanırken talihsiz şeylerin olmasını önlemek için bu meta etiketleri de göz önünde bulundurun:

<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

Söz konusu hedeflenen öğenin kaydırmak istediğiniz öğe olmadığından emin olmak için aşırı kaydırma devre dışı bırakma kodunuza biraz daha mantık ekleyebilir misiniz? Bunun gibi bir şey:

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

3
Böyle teşekkürler ... görünüyor olmalı çalışmak, ancak öyle değil. Ayrıca, "kaydırılabilir" ve ". Kaydırılabilir" (noktalı) olmamalı mı?
Jeff

1
Dokunma olayını alan en derinlemesine iç içe geçmiş öğe gibi görünüyor, bu nedenle kaydırılabilir bir div'de olup olmadığınızı görmek için tüm ebeveynlerinizi kontrol etmeniz gerekebilir.
Christopher Johnson

3
JQuery kullanılıyorsa neden document.body.addEventListener kullanılır? Bunun bir sebebi var mı?
fnagel

7

Bunun için en iyi çözüm css / html'dir: Zaten sahip değilseniz, öğelerinizi sarmak için bir div oluşturun Ve bunu sabit ve taşma gizli olarak ayarlayın. İsteğe bağlı, tüm ekranı ve tüm ekranı doldurmasını istiyorsanız, yüksekliği ve genişliği% 100 olarak ayarlayın.

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


5

Kaydırılabilir öğenin, aşağı kaydırmaya çalışırken yukarı veya aşağı kaydırmaya çalışırken ve ardından tüm sayfanın hareketini durdurmak için varsayılan eylemi engellemeye çalışırken zaten yukarı kaydırılmış olup olmadığını kontrol edin.

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

E.originalEvent.pageY yerine e.originalEvent.touches [0] .pageY'yi kontrol etmem gerekti. İşe yaradı, ancak yalnızca kayan bölmenin sonundaysanız. Kaydırma işlemi devam ederken (örneğin, gerçekten hızlı kaydırdığınızda), kaydırılabilir bölmenin sonuna ulaşıldığında durmaz.
Adaçayı Keen

4

Kaydırılabilir bir alana sahip bir pop-up (alışveriş sepetinizin kaydırılabilir bir görünümüne sahip bir "alışveriş sepeti" açılır penceresi) varken tüm vücut kaydırmasını önlemenin bir yolunu arıyordum.

Kaydırmak istediğiniz bir pop-up veya div varken vücudunuzda "noscroll" sınıfını değiştirmek için minimal javascript kullanarak çok daha zarif bir çözüm yazdım (tüm sayfa gövdesini "fazla kaydırmak" yerine).

masaüstü tarayıcılar taşma: gizli - konumu sabit olarak ayarlamadığınız sürece iOS bunu göz ardı ediyor gibi görünüyor ... bu da tüm sayfanın garip bir genişliğe sahip olmasına neden oluyor, bu nedenle konumu ve genişliği manuel olarak da ayarlamanız gerekiyor. bu css'i kullanın:

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

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

Bu çok yardımcı ve minimal bir yaklaşım, tam da ihtiyacım olan şey. Üst: 0 ile konumu sabit olarak ayarlama; sol: 0; genişlik:% 100; eksik olan unsurlardı. Bu aynı zamanda açılır menüler için de kullanışlıdır.
bdanin

3

Jquery olmadan biraz geçici çalışıyorum. Perfert değil ama iyi çalışıyor (özellikle bir scoll-y'de bir kaydırma-x'iniz varsa) https://github.com/pinadesign/overscroll/

Katılmak ve iyileştirmek için özgür düştü


1
Jeff'le aynı sorunu yaşadım, her cevabı denedim, seninki işe yaradı. Teşekkür ederim!
Dominik Schreiber

Kabul edilen yanıt, yalnızca .scrollable'a sahip div içeriğin taşmasına neden olacak kadar içeriğe sahip olduğunda işe yaradı. Taşmadıysa, 'sıçrama' etkisi hala mevcuttu. Ancak bu mükemmel çalışıyor, teşekkürler!
Adam Marshall

1

Bu çözüm, kaydırılabilir tüm div'lerinize kaydırılabilir bir sınıf koymanızı gerektirmez, bu nedenle daha geneldir. INPUT öğelerinin alt öğeleri olan veya bunların alt öğesi olan tüm öğelerde kaydırmaya izin verilir.

Özel bir seçici kullanıyorum ve ayrıca performansı artırmak için öğedeki denetimin sonucunu önbelleğe alıyorum. Her seferinde aynı öğeyi kontrol etmeye gerek yok. Bunun sadece yazılan ama paylaşacağımı düşündüğü birkaç sorunu olabilir.

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

Tüm "touchmove" olaylarını devre dışı bırakmak iyi bir fikir gibi görünebilir, ancak sayfada diğer kaydırılabilir öğelere ihtiyaç duyduğunuzda sorunlara neden olur. Bunun da ötesinde, belirli öğelerdeki (ör. Sayfanın kaydırılamaz olmasını istiyorsanız gövde) "dokunma hareketi" etkinliklerini devre dışı bırakırsanız, başka bir yerde etkinleştirilir etkinleştirilmez, IOS Chrome'da URL'de durdurulamaz bar geçişleri.

Bu davranışı açıklayamasam da, önlemenin tek yolu vücudun pozisyonunu ayarlamak gibi görünüyor fixed. Yaptığınız tek sorun, belgenin konumunu kaybetmenizdir - bu, örneğin kiplerde özellikle can sıkıcıdır. Bunu çözmenin bir yolu, bu basit VanillaJS işlevlerini kullanmaktır:

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

Bu çözümü kullanarak sabit bir belgeye sahip olabilirsiniz ve sayfanızdaki herhangi bir öğe basit CSS (örn overflow: scroll;.) Kullanarak taşabilir . Özel derslere veya başka bir şeye gerek yok.


0

İşte zepto uyumlu bir çözüm

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

0

bu benim için çalışıyor (sade 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

Bunu dene Mükemmel çalışacak.

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

Basit ile şaşırtıcı bir şansım oldu:

body {
    height: 100vh;
}

Açılır pencereler veya menüler için fazla kaydırmayı devre dışı bırakmak harika çalışıyor ve tarayıcı çubuklarını konum kullanılırken olduğu gibi görünmeye zorlamıyor: sabit. AMA - sabit yüksekliği ayarlamadan önce kaydırma konumunu kaydetmeniz ve açılır pencereyi gizlerken geri yüklemeniz gerekir, aksi takdirde tarayıcı en üste kayacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.