iOS Safari - จะปิดการใช้งานการเลื่อนทับได้อย่างไร แต่อนุญาตให้ div แบบเลื่อนเลื่อนได้ตามปกติ


103

ฉันกำลังทำงานกับเว็บแอปที่ใช้ iPad และจำเป็นต้องป้องกันการเลื่อนมากเกินไปเพื่อให้ดูเหมือนหน้าเว็บน้อยลง ฉันกำลังใช้สิ่งนี้เพื่อหยุดวิวพอร์ตและปิดใช้งานการเลื่อนทับ:

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

นี้ทำงานได้ดีในการปิดการใช้งานเลื่อน แต่ app ของฉันมี divs เลื่อนหลายและป้องกันโค้ดข้างต้นพวกเขาจากการเลื่อน

ฉันกำหนดเป้าหมายเป็น iOS 5 ขึ้นไปเท่านั้นดังนั้นฉันจึงหลีกเลี่ยงโซลูชันแฮ็กเช่น iScroll แต่ฉันใช้ CSS นี้สำหรับ div ที่เลื่อนได้ของฉัน:

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

วิธีนี้ใช้งานได้โดยไม่ต้องใช้สคริปต์ overscroll เอกสาร แต่ไม่ได้แก้ปัญหาการเลื่อน div

หากไม่มีปลั๊กอิน jQueryมีวิธีใดบ้างที่จะใช้การแก้ไขแบบโอเวอร์สโคล แต่ยกเว้น div $ ('. scrollable') ของฉัน

แก้ไข:

ฉันพบสิ่งที่เป็นทางออกที่ดี:

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

วิวพอร์ตยังคงเคลื่อนที่เมื่อคุณเลื่อนผ่านจุดเริ่มต้นหรือจุดสิ้นสุดของ div ฉันต้องการหาวิธีปิดการใช้งานเช่นกัน


ลองครั้งสุดท้ายของคุณแล้ว แต่ก็ไม่ได้ผลเช่นกัน
Santiago Rebella

ฉันสามารถป้องกันไม่ให้วิวพอร์ตเคลื่อนไหวเมื่อคุณเลื่อนผ่านจุดสิ้นสุดของ div โดยการจับเหตุการณ์การเลื่อนอย่างชัดเจนบนพาเรนต์ของ div ที่เลื่อนได้และไม่อนุญาตให้เลื่อนจริง หากคุณใช้ jquery mobile คุณควรทำสิ่งนี้ที่ระดับเพจดังนี้ $ ('div [data-role = "page"]') on ('scroll', function (e) {e.preventDefault ();});
Christopher Johnson

github.com/lazd/iNoBounceทำงานได้อย่างมหัศจรรย์
David Underhill

ฉันพบสคริปต์นี้ซึ่งแก้ไขปัญหานี้ได้! :) github.com/lazd/iNoBounce
ม.ค. Šafránek

ทำไมคุณต้องโพสต์ลิงก์อีกครั้งหากมีคนที่อยู่เหนือโพสต์ของคุณโพสต์เมื่อ 7 เดือนก่อน
Denny

คำตอบ:


84

วิธีนี้จะช่วยแก้ปัญหาเมื่อคุณเลื่อนผ่านจุดเริ่มต้นหรือจุดสิ้นสุดของ 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();
});

โปรดทราบว่าจะใช้ไม่ได้หากคุณต้องการบล็อกการเลื่อนทั้งหน้าเมื่อ div ไม่มีโอเวอร์โฟลว์ หากต้องการบล็อกให้ใช้ตัวจัดการเหตุการณ์ต่อไปนี้แทนตัวจัดการเหตุการณ์ด้านบนทันที (ดัดแปลงจากคำถามนี้ ):

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

วิธีนี้จะใช้ไม่ได้หากมี iframe อยู่ในพื้นที่ที่เลื่อนได้และผู้ใช้เริ่มเลื่อนบน iframe นั้น มีวิธีแก้ปัญหานี้หรือไม่?
Timo

2
ทำงานได้ดี - ดีกว่าการกำหนดเป้าหมาย.scrollableโดยตรง (ซึ่งเป็นสิ่งที่ฉันได้พยายามแก้ปัญหานี้ในตอนแรก) หากคุณเป็น JavaScript noob และต้องการโค้ดที่ง่ายในการลบตัวจัดการเหล่านี้ออกไปที่ไหนสักแห่งในบรรทัดสองบรรทัดนี้เหมาะกับฉันมาก! $(document).off('touchmove'); และ $('body').off('touchmove touchstart', '.scrollable');
Devin

มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ขอบคุณมากคุณช่วยฉันได้หลายชั่วโมง!
marcgg

1
วิธีนี้ใช้ไม่ได้หากมีเนื้อหาไม่เพียงพอใน div ที่จะเลื่อน มีคนตั้งคำถามแยกต่างหากที่ตอบได้ที่นี่: stackoverflow.com/q/16437182/40352
คริส

ฉันจะอนุญาตคลาส ".scrollable" มากกว่าหนึ่งคลาสได้อย่างไร มันใช้งานได้ดี แต่ฉันต้องทำให้ div อื่นเลื่อนได้ด้วย ขอบคุณ!
MeV

23

การใช้คำตอบที่ยอดเยี่ยมของ Tyler Dodge ทำให้ iPad ของฉันล้าหลังดังนั้นฉันจึงเพิ่มรหัสควบคุมปริมาณตอนนี้มันค่อนข้างราบรื่น มีการข้ามน้อยที่สุดในบางครั้งขณะเลื่อน

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

นอกจากนี้การเพิ่ม CSS ต่อไปนี้จะช่วยแก้ไขข้อบกพร่องในการแสดงผล (ที่มา ):

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

วิธีนี้จะใช้ไม่ได้หากมี iframe อยู่ในพื้นที่ที่เลื่อนได้และผู้ใช้เริ่มเลื่อนบน iframe นั้น มีวิธีแก้ปัญหานี้หรือไม่?
Timo

1
ดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์แบบสำหรับการลากไปข้างหลัง แต่การลากลงด้านล่างจะยังคงเป็นการย้ายซาฟารี
Abadaba

1
วิธีแก้ปัญหาที่ยอดเยี่ยม ... ขอบคุณมาก :)
Aamir Shah

สิ่งนี้ได้ผลสำหรับฉัน ขอบคุณ! ฉันใช้เวลามากกว่า 1.5 วันในการแก้ไขปัญหานี้
Achintha Samindika

มันยอดเยี่ยมมากทำงานได้ดีและช่วยให้ฉันหายเครียดมากขึ้นในการพยายามหาวิธีแก้ปัญหา ขอบคุณ Kuba!
Leonard

12

ขั้นแรกป้องกันการดำเนินการเริ่มต้นกับเอกสารทั้งหมดของคุณตามปกติ:

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

จากนั้นหยุดคลาสองค์ประกอบของคุณไม่ให้แพร่กระจายไปยังระดับเอกสาร สิ่งนี้จะหยุดไม่ให้เข้าถึงฟังก์ชันด้านบนดังนั้นจึงไม่เริ่มต้น e.preventDefault ():

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

ระบบนี้ดูเหมือนจะเป็นธรรมชาติมากกว่าและใช้ความเข้มข้นน้อยกว่าการคำนวณคลาสด้วยการสัมผัสทั้งหมด ใช้. on () แทน. bind () สำหรับองค์ประกอบที่สร้างแบบไดนามิก

พิจารณาเมตาแท็กเหล่านี้ด้วยเพื่อป้องกันไม่ให้สิ่งอัปมงคลเกิดขึ้นขณะใช้ div ที่เลื่อนได้ของคุณ:

<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

คุณสามารถเพิ่มตรรกะอีกเล็กน้อยในรหัสปิดการใช้งานการเลื่อนทับของคุณเพื่อให้แน่ใจว่าองค์ประกอบเป้าหมายที่เป็นปัญหาไม่ใช่สิ่งที่คุณต้องการเลื่อนหรือไม่? สิ่งนี้:

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

3
ขอบคุณ ... ดูเหมือนว่าจะได้ผล แต่ก็ไม่เป็นเช่นนั้น นอกจากนี้ไม่ควรเป็น "scrollable" และไม่ใช่ ".scrollable" (มีจุด)
เจฟฟ์

1
ดูเหมือนว่าเป็นองค์ประกอบที่ซ้อนกันลึกที่สุดที่รับเหตุการณ์สัมผัสดังนั้นคุณอาจต้องตรวจสอบผู้ปกครองทั้งหมดเพื่อดูว่าคุณอยู่ใน div ที่เลื่อนได้หรือไม่
Christopher Johnson

3
เหตุใดจึงต้องใช้ document.body.addEventListener ถ้าใช้ jQuery นั่นเป็นเหตุผล?
fnagel

7

ทางออกที่ดีที่สุดคือ css / html: สร้าง div เพื่อรวมองค์ประกอบของคุณหากคุณยังไม่มีและตั้งค่าเป็นตำแหน่งคงที่และซ่อนส่วนเกิน ทางเลือกกำหนดความสูงและความกว้างเป็น 100% หากคุณต้องการให้เต็มหน้าจอและไม่มีอะไรเลยนอกจากทั้งหน้าจอ

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


5

ตรวจสอบว่าองค์ประกอบที่เลื่อนได้เลื่อนไปด้านบนแล้วหรือไม่เมื่อพยายามเลื่อนขึ้นหรือลงด้านล่างเมื่อพยายามเลื่อนลงจากนั้นป้องกันไม่ให้การดำเนินการเริ่มต้นหยุดทั้งหน้าไม่ให้เคลื่อนที่

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.touches [0] .pageY แทน e.originalEvent.pageY มันใช้งานได้ แต่ถ้าคุณอยู่ในตอนท้ายของ div เลื่อนแล้ว เมื่อการเลื่อนอยู่ระหว่างดำเนินการ (เช่นคุณเลื่อนเร็วมาก) จะไม่หยุดเมื่อถึงจุดสิ้นสุดของ div ที่เลื่อนได้
Keen Sage

4

ฉันกำลังมองหาวิธีป้องกันการเลื่อนร่างกายทั้งหมดเมื่อมีป๊อปอัปที่มีพื้นที่เลื่อนได้ (ป๊อปดาวน์ "ตะกร้าสินค้า" ที่มีมุมมองที่เลื่อนได้ของรถเข็นของคุณ)

ฉันเขียนวิธีการแก้ปัญหาที่หรูหรากว่ามากโดยใช้จาวาสคริปต์ที่เรียบง่ายเพื่อสลับคลาส "noscroll" ในเนื้อหาของคุณเมื่อคุณมีป๊อปอัปหรือ div ที่คุณต้องการเลื่อน (ไม่ใช่ "เลื่อนเกิน" ทั้งเนื้อหาของหน้า)

ในขณะที่เบราว์เซอร์เดสก์ท็อปสังเกตเห็น overflow: hidden - ดูเหมือนว่า iOS จะเพิกเฉยต่อสิ่งนั้นเว้นแต่คุณจะตั้งค่าตำแหน่งเป็นคงที่ ... ซึ่งทำให้ทั้งหน้ามีความกว้างแปลก ๆ ดังนั้นคุณต้องกำหนดตำแหน่งและความกว้างด้วยตนเองเช่นกัน ใช้ css นี้:

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

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

สิ่งนี้มีประโยชน์มากและเป็นแนวทางที่เรียบง่ายสิ่งที่ฉันต้องการ การตั้งค่าตำแหน่งคงที่โดยด้านบน: 0; ซ้าย: 0; ความกว้าง: 100%; เป็นองค์ประกอบที่ฉันขาดหายไป นอกจากนี้ยังมีประโยชน์สำหรับเมนูบินออก
bdanin

3

ฉันทำงานเล็กน้อยโดยไม่มี jquery ไม่สมบูรณ์ แต่ใช้งานได้ดี (โดยเฉพาะถ้าคุณมี scroll-x ใน scoll-y) https://github.com/pinadesign/overscroll/

อย่าลังเลที่จะเข้าร่วมและปรับปรุง


1
มีปัญหาเดียวกับเจฟฟ์ลองทุกคำตอบของคุณได้ผล ขอบคุณ!
Dominik Schreiber

คำตอบที่ยอมรับจะใช้ได้ผลกับฉันก็ต่อเมื่อ div ที่มี. scrollable มีเนื้อหาเพียงพอที่จะทำให้มันล้น หากไม่ล้นออกมาจะยังคงมีเอฟเฟกต์ "ตีกลับ" อย่างไรก็ตามมันทำงานได้อย่างสมบูรณ์ขอบคุณ!
Adam Marshall

1

โซลูชันนี้ไม่ต้องการให้คุณใส่คลาสแบบเลื่อนลงใน div ที่เลื่อนได้ทั้งหมดของคุณ อนุญาตให้มีการเลื่อนสำหรับองค์ประกอบทั้งหมดที่เป็นหรือเป็นลูกขององค์ประกอบ INPUT ที่สามารถแก้ไขได้และการเลื่อนหรือรถยนต์มากเกินไป

ฉันใช้ตัวเลือกแบบกำหนดเองและฉันยังแคชผลลัพธ์ของการตรวจสอบในองค์ประกอบเพื่อปรับปรุงประสิทธิภาพ ไม่จำเป็นต้องตรวจสอบองค์ประกอบเดียวกันทุกครั้ง อาจมีปัญหาเล็กน้อยเนื่องจากเพิ่งเขียน แต่คิดว่าจะแชร์

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

แม้ว่าการปิดใช้งานกิจกรรม "touchmove" ทั้งหมดอาจดูเหมือนเป็นความคิดที่ดี แต่ทันทีที่คุณต้องการองค์ประกอบที่เลื่อนได้อื่น ๆ บนหน้าเว็บจะทำให้เกิดปัญหา ยิ่งไปกว่านั้นหากคุณปิดใช้งานเหตุการณ์ "touchmove" ในองค์ประกอบบางอย่างเท่านั้น (เช่นเนื้อหาหากคุณต้องการให้เพจไม่สามารถเลื่อนได้) ทันทีที่เปิดใช้งานที่อื่น IOS จะทำให้การเผยแพร่ที่ไม่สามารถหยุดได้ใน Chrome เมื่อ URL สลับบาร์

fixedในขณะที่ฉันไม่สามารถอธิบายพฤติกรรมนี้ดูเหมือนว่าวิธีการป้องกันเท่านั้นดูเหมือนว่าจะตั้งตำแหน่งของร่างกายในการ ปัญหาเดียวที่ทำคือคุณจะสูญเสียตำแหน่งของเอกสารซึ่งเป็นเรื่องที่น่ารำคาญอย่างยิ่งในรูปแบบต่างๆเช่น วิธีหนึ่งในการแก้ปัญหาคือการใช้ฟังก์ชัน 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);
    }
}

การใช้โซลูชันนี้คุณสามารถมีเอกสารคงที่และองค์ประกอบอื่น ๆ ในเพจของคุณสามารถล้นได้โดยใช้ CSS แบบง่าย (เช่นoverflow: scroll;) ไม่จำเป็นต้องเรียนพิเศษหรืออย่างอื่น



0

อันนี้ใช้ได้กับฉัน (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

ฉันโชคดีอย่างน่าประหลาดใจกับเรื่องง่ายๆ:

body {
    height: 100vh;
}

ใช้งานได้ดีในการปิดใช้งานการเลื่อนทับสำหรับป๊อปอัปหรือเมนูและไม่บังคับให้แถบเบราว์เซอร์ปรากฏเหมือนเมื่อใช้ตำแหน่ง: คงที่ แต่ - คุณต้องบันทึกตำแหน่งการเลื่อนก่อนที่จะตั้งค่าความสูงคงที่และเรียกคืนเมื่อซ่อนป๊อปอัปมิฉะนั้นเบราว์เซอร์จะเลื่อนไปด้านบน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.