iOS Safari - Làm cách nào để tắt tính năng cuộn quá mức nhưng cho phép các dấu chia có thể cuộn được cuộn bình thường?


100

Tôi đang làm việc trên một ứng dụng web dựa trên iPad và cần ngăn việc cuộn quá mức để nó có vẻ giống một trang web hơn. Tôi hiện đang sử dụng cái này để đóng băng chế độ xem và vô hiệu hóa quá trình cuộn:

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

Điều này hoạt động tuyệt vời để tắt cuộn quá mức nhưng ứng dụng của tôi có một số div có thể cuộn và đoạn mã trên ngăn chúng cuộn .

Tôi chỉ nhắm mục tiêu iOS 5 trở lên vì vậy tôi đã tránh các giải pháp hacky như iScroll. Thay vào đó, tôi đang sử dụng CSS này cho các div có thể cuộn của mình:

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

Điều này hoạt động mà không cần tập lệnh cuộn qua tài liệu, nhưng không giải quyết được vấn đề cuộn div.

Nếu không có plugin jQuery, có cách nào để sử dụng bản sửa lỗi quá cuộn nhưng miễn các div $ ('. Scrollable') của tôi không?

BIÊN TẬP:

Tôi đã tìm thấy một cái gì đó là một giải pháp tốt:

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

Khung nhìn vẫn di chuyển khi bạn cuộn qua phần đầu hoặc phần cuối của div. Tôi cũng muốn tìm cách vô hiệu hóa điều đó.


cố gắng một mình cuối cùng cũng nhưng didnt làm việc một trong hai
Santiago Rebella

Tôi đã có thể giữ cho chế độ xem không di chuyển khi bạn cuộn qua phần cuối của div bằng cách nắm bắt rõ ràng sự kiện cuộn trên phần mẹ của div có thể cuộn và không cho phép nó thực sự cuộn. Nếu bạn đang sử dụng jquery mobile, bạn nên thực hiện việc này ở cấp trang như sau: $ ('div [data-role = "page"]'). On ('scroll', function (e) {e.preventDefault ();});
Christopher Johnson

github.com/lazd/iNoBounce công trình kỳ diệu
David Underhill

Tôi đã tìm thấy tập lệnh này để khắc phục sự cố này! :) github.com/lazd/iNoBounce
Jan Šafránek

Tại sao bạn lại đăng liên kết nếu ai đó ở trên bài đăng của bạn, đăng nó 7 tháng trước đó?
Denny

Câu trả lời:


84

Điều này giải quyết vấn đề khi bạn cuộn qua phần đầu hoặc phần cuối của 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();
});

Lưu ý rằng điều này sẽ không hoạt động nếu bạn muốn chặn cuộn toàn bộ trang khi một div không bị tràn. Để chặn điều đó, hãy sử dụng trình xử lý sự kiện sau thay vì trình xử lý sự kiện ngay lập tức ở trên (phỏng theo câu hỏi này ):

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

Điều này sẽ không hoạt động nếu có một iframe bên trong vùng có thể cuộn và người dùng bắt đầu cuộn trên iframe đó. Có một cách giải quyết cho điều này?
Timo

2
Hoạt động tốt - điều này chắc chắn tốt hơn là chỉ nhắm mục tiêu .scrollabletrực tiếp (đó là những gì tôi đã thử ban đầu để giải quyết vấn đề này). Nếu bạn là một noob JavaScript và muốn có mã dễ dàng để xóa các trình xử lý này ở đâu đó dưới dòng, hai dòng này rất phù hợp với tôi! $(document).off('touchmove');$('body').off('touchmove touchstart', '.scrollable');
Devin

Nó làm việc hoàn hảo cho tôi. Cảm ơn rất nhiều, bạn đã tiết kiệm cho tôi giờ!
marcgg

1
Điều này không hoạt động nếu không có đủ nội dung trong div để cuộn. Ai đó làm một câu hỏi riêng biệt mà trả lời rằng ở đây: stackoverflow.com/q/16437182/40352
Chris

Làm cách nào để cho phép nhiều hơn một lớp ".scrollable"? nó hoạt động tốt với một div nhưng tôi cũng cần tạo một div khác có thể cuộn được. Cảm ơn!
MeV

23

Sử dụng câu trả lời xuất sắc của Tyler Dodge liên tục bị trễ trên iPad của tôi, vì vậy tôi đã thêm một số mã điều chỉnh, bây giờ nó khá trơn tru. Đôi khi có một số bỏ qua tối thiểu trong khi cuộn.

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

Ngoài ra, việc thêm CSS sau sẽ khắc phục một số trục trặc hiển thị ( nguồn ):

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

Điều này sẽ không hoạt động nếu có một iframe bên trong vùng có thể cuộn và người dùng bắt đầu cuộn trên iframe đó. Có một cách giải quyết cho điều này?
Timo

1
Có vẻ như hoạt động hoàn hảo để kéo về phía sau, nhưng kéo xuống dưới vẫn sẽ di chuyển safari.
Abadaba

1
Một giải pháp tuyệt vời ... Thanks a lot :)
Aamir Shah

Điều này đã làm việc cho tôi. Cảm ơn! Tôi dành hơn 1,5 ngày để giải quyết vấn đề này.
Achintha Samindika

Điều này thật tuyệt vời, hiệu quả tuyệt vời và giúp tôi không còn căng thẳng khi cố gắng tìm ra giải pháp. Cảm ơn Kuba!
Leonard

12

Trước tiên, hãy ngăn các hành động mặc định trên toàn bộ tài liệu của bạn như bình thường:

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

Sau đó, ngăn không cho lớp phần tử của bạn lan truyền đến cấp tài liệu. Điều này ngăn nó đến chức năng trên và do đó e.preventDefault () không được khởi tạo:

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

Hệ thống này dường như tự nhiên hơn và ít chuyên sâu hơn so với tính toán lớp trên tất cả các động tác chạm. Sử dụng .on () thay vì .bind () cho các phần tử được tạo động.

Cũng nên xem xét các thẻ meta này để ngăn những điều không may xảy ra khi sử dụng div có thể cuộn của bạn:

<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

Bạn có thể thêm một chút logic vào mã vô hiệu hóa quá trình cuộn để đảm bảo rằng phần tử được nhắm mục tiêu được đề cập không phải là phần tử mà bạn muốn cuộn không? Một cái gì đó như thế này:

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

3
Cảm ơn ... Có vẻ như điều này sẽ hoạt động, nhưng nó không. Ngoài ra, nó không phải là "scrollable" và không phải là ".scrollable" (với dấu chấm)?
Jeff

1
Có vẻ như đây là phần tử được lồng sâu nhất nhận sự kiện chạm nên bạn có thể cần kiểm tra tất cả các phần tử cha mẹ của mình để xem liệu bạn có ở trong một div có thể cuộn được hay không.
Christopher Johnson

3
Tại sao người ta sẽ sử dụng document.body.addEventListener nếu jQuery được sử dụng? Đó là vì một lý do?
fnagel

7

Giải pháp tốt nhất cho điều này là css / html: Tạo một div để bao bọc các phần tử của bạn, nếu bạn chưa có và đặt nó ở vị trí cố định và ẩn tràn. Tùy chọn, đặt chiều cao và chiều rộng thành 100% nếu bạn muốn nó lấp đầy toàn bộ màn hình và không có gì ngoài toàn bộ màn hình

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


5

Kiểm tra xem phần tử có thể cuộn đã được cuộn lên trên cùng khi cố gắng cuộn lên hay xuống cuối khi cố cuộn xuống rồi ngăn hành động mặc định để dừng toàn bộ trang di chuyển hay chưa.

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

Tôi đã phải kiểm tra e.originalEvent.touches [0] .pageY thay vì e.originalEvent.pageY. Nó hoạt động nhưng chỉ khi bạn đã ở cuối div cuộn. Khi cuộn đang được tiến hành (ví dụ: bạn đã cuộn rất nhanh), nó không dừng lại khi đạt đến cuối div có thể cuộn.
Keen Sage,

4

Tôi đang tìm cách để ngăn việc cuộn toàn bộ cơ thể khi có một cửa sổ bật lên với khu vực có thể cuộn được (cửa sổ bật xuống "giỏ hàng" có chế độ xem có thể cuộn của giỏ hàng của bạn).

Tôi đã viết một giải pháp thanh lịch hơn nhiều bằng cách sử dụng javascript tối thiểu để chỉ chuyển đổi lớp "noscroll" trên nội dung của bạn khi bạn có cửa sổ bật lên hoặc div mà bạn muốn cuộn (chứ không phải "cuộn qua" toàn bộ nội dung trang).

trong khi trình duyệt máy tính để bàn quan sát tràn: ẩn - iOS dường như bỏ qua điều đó trừ khi bạn đặt vị trí thành cố định ... khiến toàn bộ trang có chiều rộng kỳ lạ, vì vậy bạn cũng phải đặt vị trí và chiều rộng theo cách thủ công. sử dụng css này:

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

và jquery này:

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

Điều này rất hữu ích và là một cách tiếp cận tối thiểu, chỉ những gì tôi cần. Đặt vị trí thành cố định, với đầu: 0; trái: 0; chiều rộng: 100%; là những yếu tố tôi đã thiếu. Điều này cũng hữu ích cho các menu bay ra.
bdanin

3

Tôi đã thực hiện một giải pháp nhỏ mà không cần jquery. Không phải perfert nhưng hoạt động tốt (đặc biệt nếu bạn có một cuộn-x trong một scout-y) https://github.com/pinadesign/overscroll/

Hãy tự do tham gia và cải thiện nó


1
Gặp vấn đề tương tự như Jeff, hãy thử mọi câu trả lời, câu trả lời của bạn đã thành công. Cảm ơn bạn!
Dominik Schreiber,

Câu trả lời được chấp nhận chỉ phù hợp với tôi khi div có .scrollable có đủ nội dung để khiến nó bị tràn. Nếu nó không tràn, hiệu ứng 'thoát' vẫn tồn tại. Tuy nhiên điều này hoạt động hoàn hảo, cảm ơn!
Adam Marshall

1

Giải pháp này không yêu cầu bạn phải đặt một lớp có thể cuộn lên trên tất cả các div có thể cuộn của bạn, vì vậy nó sẽ tổng quát hơn. Cho phép cuộn trên tất cả các phần tử có hoặc là con của các phần tử INPUT có nội dung và cuộn tràn hoặc autos.

Tôi sử dụng bộ chọn tùy chỉnh và tôi cũng lưu vào bộ nhớ cache kết quả kiểm tra phần tử để cải thiện hiệu suất. Không cần phải kiểm tra cùng một phần tử mỗi lần. Điều này có thể có một số vấn đề như chỉ mới được viết nhưng tôi nghĩ rằng tôi muốn chia sẻ.

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

Mặc dù tắt tất cả các sự kiện "touchmove" có vẻ là một ý tưởng hay, nhưng ngay khi bạn cần các phần tử có thể cuộn khác trên trang, điều đó sẽ gây ra sự cố. Trên hết, nếu bạn chỉ tắt các sự kiện "touchmove" trên một số phần tử nhất định (ví dụ: body nếu bạn muốn trang không thể cuộn được), ngay khi nó được bật ở bất kỳ nơi nào khác, IOS sẽ gây ra sự lan truyền không thể ngăn cản trong Chrome khi URL thanh chuyển đổi.

Mặc dù tôi không thể giải thích hành vi này, nhưng có vẻ như cách duy nhất để ngăn chặn là đặt vị trí của cơ thể thành fixed. Vấn đề duy nhất đang làm là bạn sẽ mất vị trí của tài liệu - ví dụ, điều này đặc biệt khó chịu trong các chế độ. Một cách để giải quyết nó là sử dụng các hàm VanillaJS đơn giản sau:

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

Sử dụng giải pháp này, bạn có thể có một tài liệu cố định và bất kỳ phần tử nào khác trong trang của bạn có thể bị tràn bằng cách sử dụng CSS đơn giản (ví dụ overflow: scroll;:). Không cần các lớp học đặc biệt hoặc bất cứ điều gì khác.


0

Đây là một giải pháp tương thích với 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

cái này phù hợp với tôi (javascript đơn giản)

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

Hãy thử điều này Nó sẽ hoạt động hoàn hảo.

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

Tôi đã gặp may mắn đáng ngạc nhiên với đơn giản:

body {
    height: 100vh;
}

Nó hoạt động tuyệt vời để tắt cuộn quá mức cho cửa sổ bật lên hoặc menu và nó không buộc các thanh trình duyệt xuất hiện như khi sử dụng vị trí: fixed. NHƯNG - bạn cần lưu vị trí cuộn trước khi đặt chiều cao cố định và khôi phục nó khi ẩn cửa sổ bật lên, nếu không, trình duyệt sẽ cuộn lên trên cùng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.