iOS Safari - Bagaimana cara menonaktifkan overscroll tetapi mengizinkan div yang dapat digulir untuk menggulir secara normal?

100

Saya sedang mengerjakan aplikasi web berbasis iPad, dan perlu mencegah overscrolling sehingga tidak terlihat seperti halaman web. Saat ini saya menggunakan ini untuk membekukan viewport dan menonaktifkan overscroll:

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

Ini berfungsi dengan baik untuk menonaktifkan overscroll tetapi aplikasi saya memiliki beberapa div yang dapat digulir, dan kode di atas mencegahnya menggulir .

Saya hanya menargetkan iOS 5 dan yang lebih baru jadi saya telah menghindari solusi peretasan seperti iScroll. Sebagai gantinya saya menggunakan CSS ini untuk div saya yang dapat digulir:

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

Ini berfungsi tanpa skrip overscroll dokumen, tetapi tidak menyelesaikan masalah pengguliran div.

Tanpa plugin jQuery, apakah ada cara untuk menggunakan perbaikan overscroll tetapi mengecualikan div $ ('. Scrollable') saya?

EDIT:

Saya menemukan sesuatu yang merupakan solusi yang layak:

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

Area pandang masih bergerak saat Anda menggulir melewati awal atau akhir div. Saya ingin menemukan cara untuk menonaktifkannya juga.

Jeff
sumber
mencoba yang terakhir juga tetapi tidak berhasil
Santiago Rebella
Saya dapat menjaga area pandang agar tidak bergerak saat Anda menggulir melewati akhir div dengan secara eksplisit menangkap acara gulir pada induk div yang dapat digulir dan tidak mengizinkannya untuk benar-benar menggulir. Jika Anda menggunakan jquery mobile, masuk akal untuk melakukan ini di tingkat halaman seperti: $ ('div [data-role = "page"]'). On ('scroll', function (e) {e.preventDefault ();});
Christopher Johnson
github.com/lazd/iNoBounce bekerja dengan sangat baik
David Underhill
Saya telah menemukan skrip ini yang memperbaiki masalah ini! :) github.com/lazd/iNoBounce
Jan Šafránek
Mengapa Anda memposting link lagi jika seseorang di atas postingan Anda, mempostingnya 7 bulan sebelumnya?
Denny

Jawaban:

84

Ini menyelesaikan masalah saat Anda menggulir melewati awal atau akhir 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();
});

Perhatikan bahwa ini tidak akan berfungsi jika Anda ingin memblokir seluruh pengguliran halaman saat div tidak memiliki overflow. Untuk memblokirnya, gunakan event handler berikut, bukan yang langsung di atas (diadaptasi dari pertanyaan ini ):

$('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
sumber
Ini tidak akan berfungsi jika ada iframe di dalam area yang dapat digulir dan pengguna mulai menggulir iframe tersebut. Apakah ada solusi untuk ini?
Timo
2
Berhasil - ini jelas lebih baik daripada hanya menargetkan .scrollablesecara langsung (yang awalnya saya coba dalam memecahkan masalah ini). Jika Anda seorang pemula JavaScript dan ingin kode mudah untuk menghapus penangan ini di suatu tempat, kedua baris ini bekerja dengan baik untuk saya! $(document).off('touchmove'); DAN $('body').off('touchmove touchstart', '.scrollable');
Devin
Ini bekerja dengan sempurna untuk saya. Terima kasih banyak, Anda menghemat waktu berjam-jam!
marcgg
1
Ini tidak berfungsi jika tidak ada cukup konten di div untuk menggulir. Seseorang membuat pertanyaan terpisah yang menjawabnya di sini: stackoverflow.com/q/16437182/40352
Chris
Bagaimana cara mengizinkan lebih dari satu kelas ".scrollable"? itu berfungsi dengan baik dengan satu tetapi saya perlu membuat div lain yang dapat digulir juga. Terima kasih!
MeV
23

Menggunakan jawaban Tyler Dodge yang luar biasa terus tertinggal di iPad saya, jadi saya menambahkan beberapa kode pelambatan, sekarang cukup lancar. Terkadang ada sedikit lompatan saat menggulir.

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

Selain itu, menambahkan CSS berikut memperbaiki beberapa gangguan rendering ( sumber ):

.scrollable {
    overflow: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}
.scrollable * {
    -webkit-transform: translate3d(0,0,0);
}
Kuba Holuj
sumber
Ini tidak akan berfungsi jika ada iframe di dalam area yang dapat digulir dan pengguna mulai menggulir iframe tersebut. Apakah ada solusi untuk ini?
Timo
1
Tampaknya berfungsi sempurna untuk menyeret mundur, tetapi menyeret ke bawah masih akan memindahkan safari.
Abadaba
1
Solusi yang luar biasa ... Terima kasih banyak :)
Aamir Shah
Ini berhasil untuk saya. Terima kasih! Saya menghabiskan waktu lebih dari 1,5 hari untuk mengatasi masalah ini.
Achintha Samindika
Ini luar biasa, bekerja dengan baik dan menyelamatkan saya dari stres lebih lanjut saat mencoba mencari solusi. Kuba terima kasih!
Leonard
12

Pertama-tama, cegah tindakan default di seluruh dokumen Anda seperti biasa:

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

Kemudian hentikan penyebaran elemen kelas Anda ke tingkat dokumen. Ini menghentikannya mencapai fungsi di atas dan dengan demikian e.preventDefault () tidak dimulai:

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

Sistem ini tampaknya lebih alami dan kurang intensif daripada menghitung kelas pada semua gerakan sentuh. Gunakan .on () daripada .bind () untuk elemen yang dibuat secara dinamis.

Juga pertimbangkan tag meta berikut untuk mencegah terjadinya hal-hal yang tidak menguntungkan saat menggunakan div yang dapat digulir:

<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
sumber
7

Bisakah Anda menambahkan sedikit lebih banyak logika ke kode penonaktifan overscroll Anda untuk memastikan elemen yang ditargetkan bukan salah satu yang ingin Anda scroll? Sesuatu seperti ini:

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });
PengembangJoe
sumber
3
Terima kasih ... Sepertinya ini seharusnya berhasil, tetapi ternyata tidak. Selain itu, bukankah seharusnya "scrollable" dan bukan ".scrollable" (dengan titik)?
Jeff
1
Sepertinya ini adalah elemen bertingkat paling dalam yang menerima peristiwa sentuh sehingga Anda mungkin perlu memeriksa semua orang tua untuk melihat apakah Anda berada dalam div yang dapat di-scroll.
Christopher Johnson
3
Mengapa seseorang menggunakan document.body.addEventListener jika jQuery digunakan? Apakah itu karena suatu alasan?
fnagel
7

Solusi terbaik untuk ini adalah css / html: Buat div untuk membungkus elemen Anda, jika Anda belum memilikinya Dan setel ke posisi tetap dan luapan tersembunyi. Opsional, atur tinggi dan lebar menjadi 100% jika Anda ingin memenuhi seluruh layar dan tidak lain adalah seluruh layar

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

Elias Fyksen
sumber
5

Periksa apakah elemen yang dapat digulir sudah digulir ke atas saat mencoba untuk menggulir ke atas atau ke bawah saat mencoba untuk menggulir ke bawah dan kemudian mencegah tindakan default untuk menghentikan seluruh halaman agar tidak bergerak.

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
sumber
Saya harus memeriksa e.originalEvent.touches [0] .pageY, bukan e.originalEvent.pageY. Ini berfungsi tetapi hanya jika Anda sudah berada di akhir div gulir. Saat pengguliran sedang berlangsung (mis. Anda telah menggulir dengan sangat cepat) itu tidak berhenti setelah akhir div yang dapat digulir tercapai.
Keen Sage
4

Saya sedang mencari cara untuk mencegah semua tubuh menggulir saat ada popup dengan area yang dapat digulir (pop-down "keranjang belanja" yang memiliki tampilan keranjang Anda yang dapat digulir).

Saya menulis solusi yang jauh lebih elegan dengan menggunakan javascript minimal untuk hanya mengaktifkan kelas "noscroll" di tubuh Anda ketika Anda memiliki popup atau div yang ingin Anda gulir (dan tidak "menggulung" seluruh badan halaman).

sementara browser desktop mengamati overflow: hidden - iOS tampaknya mengabaikannya kecuali Anda menyetel posisinya ke tetap ... yang menyebabkan seluruh halaman menjadi lebar aneh, jadi Anda juga harus menyetel posisi dan lebar secara manual. gunakan css ini:

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

dan jquery ini:

/* 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
sumber
Ini sangat membantu, dan pendekatan minimal, hanya yang saya butuhkan. Mengatur posisi menjadi tetap, dengan top: 0; kiri: 0; lebar: 100%; adalah elemen yang saya lewatkan. Ini juga berguna untuk menu fly-out.
bdanin
3

Saya telah bekerja sedikit tanpa jquery. Tidak sempurna tetapi berfungsi dengan baik (terutama jika Anda memiliki scroll-x di scoll-y) https://github.com/pinadesign/overscroll/

Bebas untuk berpartisipasi dan meningkatkannya

PiñaDesign
sumber
1
Memiliki masalah yang sama dengan Jeff, mencoba setiap jawaban, jawaban Anda berhasil. Terima kasih!
Dominik Schreiber
Jawaban yang diterima hanya bekerja untuk saya ketika div dengan .scrollable memiliki konten yang cukup untuk membuatnya meluap. Jika tidak meluap, efek 'pantulan' masih ada. Namun ini bekerja dengan sempurna, terima kasih!
Adam Marshall
1

Solusi ini tidak mengharuskan Anda untuk menempatkan kelas yang dapat digulir pada semua div yang dapat digulir, jadi lebih umum. Pengguliran diperbolehkan pada semua elemen yang merupakan, atau merupakan turunan dari, elemen INPUT yang dapat diedit konten dan pengguliran tambahan atau otomatis.

Saya menggunakan pemilih khusus dan saya juga menyimpan hasil pemeriksaan di elemen untuk meningkatkan kinerja. Tidak perlu memeriksa elemen yang sama setiap saat. Ini mungkin memiliki beberapa masalah karena baru saja ditulis tetapi saya pikir akan saya bagikan.

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

Meskipun menonaktifkan semua acara "touchmove" mungkin tampak seperti ide yang bagus, segera setelah Anda membutuhkan elemen lain yang dapat digulir pada halaman itu akan menimbulkan masalah. Selain itu, jika Anda hanya menonaktifkan peristiwa "touchmove" pada elemen tertentu (mis. Body jika Anda ingin halaman tidak dapat digulir), segera setelah diaktifkan di tempat lain, IOS akan menyebabkan penyebaran yang tidak dapat dihentikan di Chrome saat URL matikan bar.

Meskipun saya tidak dapat menjelaskan perilaku ini, sepertinya satu-satunya cara untuk mencegah tampaknya mengatur posisi tubuh fixed. Satu-satunya masalah yang dilakukan adalah Anda akan kehilangan posisi dokumen - hal ini sangat mengganggu pada modals misalnya. Salah satu cara untuk mengatasinya adalah dengan menggunakan fungsi VanillaJS sederhana ini:

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

Dengan menggunakan solusi ini, Anda dapat memiliki dokumen tetap dan elemen lain di halaman Anda dapat melimpah dengan menggunakan CSS sederhana (misalnya, overflow: scroll;). Tidak perlu kelas khusus atau apa pun.

Nicolas Bouvrette
sumber
0

Inilah solusi yang kompatibel dengan 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
sumber
0

yang ini berfungsi untuk saya (javascript biasa)

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
sumber
0

Coba ini Ini akan bekerja dengan sempurna.

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

Saya memiliki keberuntungan yang mengejutkan dengan sederhana:

body {
    height: 100vh;
}

Ini berfungsi dengan baik untuk menonaktifkan overscroll untuk pop-up atau menu dan tidak memaksa bilah browser untuk muncul seperti saat menggunakan posisi: tetap. TAPI - Anda perlu menyimpan posisi gulir sebelum mengatur ketinggian tetap dan mengembalikannya saat menyembunyikan pop-up, jika tidak, browser akan menggulir ke atas.

Láďa Durchánek
sumber