Programing

iOS Safari – 오버 스크롤을 비활성화하고 스크롤 가능한 div가 정상적으로 스크롤되도록 허용하는 방법은 무엇입니까?

crosscheck 2020. 8. 24. 07:34
반응형

iOS Safari – 오버 스크롤을 비활성화하고 스크롤 가능한 div가 정상적으로 스크롤되도록 허용하는 방법은 무엇입니까?


저는 iPad 기반 웹 앱에서 작업 중이며 웹 페이지처럼 보이지 않도록 오버 스크롤을 방지해야합니다. 나는 현재 이것을 사용하여 뷰포트를 고정하고 오버 스크롤을 비활성화합니다.

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

이것은 오버 스크롤을 비활성화하는 데 효과적이지만 내 앱에는 스크롤 가능한 div가 여러 개 있으며 위의 코드는 스크롤을 방지합니다 .

저는 iOS 5 이상 만 대상으로하고 있으므로 iScroll과 같은 해키 솔루션을 피했습니다. 대신 스크롤 가능한 div에이 CSS를 사용하고 있습니다.

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

이것은 문서 오버 스크롤 스크립트없이 작동하지만 div 스크롤 문제를 해결하지 못합니다.

jQuery 플러그인이 없으면 오버 스크롤 수정을 사용하지만 $ ( '. scrollable') div를 제외하는 방법이 있습니까?

편집하다:

괜찮은 해결책을 찾았습니다.

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

div의 시작 또는 끝을지나 스크롤해도 뷰포트는 계속 이동합니다. 나는 그것을 비활성화하는 방법을 찾고 싶습니다.


이렇게하면 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();
    }
});

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를 추가하면 일부 렌더링 결함이 수정됩니다 ( source ).

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

먼저 평소와 같이 전체 문서에 대한 기본 작업을 방지합니다.

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

그런 다음 요소 클래스가 문서 레벨로 전파되는 것을 중지하십시오. 이렇게하면 위의 함수에 도달하지 못하므로 e.preventDefault ()가 시작되지 않습니다.

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

이 시스템은 모든 터치 동작에 대한 클래스를 계산하는 것보다 더 자연스럽고 덜 집중적으로 보입니다. 동적으로 생성 된 요소에는 .bind () 대신 .on ()을 사용하십시오.

또한 스크롤 가능한 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" />

오버 스크롤 비활성화 코드에 논리를 조금 더 추가하여 문제의 대상 요소가 스크롤하려는 요소가 아닌지 확인할 수 있습니까? 이 같은:

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

Best solution to this is css/html: Make a div to wrap your elements in, if you dont have it already And set it to position fixed and overflow hidden. Optional, set height and width to 100% if you want it to fill the whole screen and nothing but the whole screen

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


Check if the scrollable element is already scrolled to the top when trying to scroll up or to the bottom when trying to scroll down and then preventing the default action to stop the entire page from moving.

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

I was looking for a way to prevent all body scrolling when there's a popup with a scrollable area (a "shopping cart" popdown that has a scrollable view of your cart).

I wrote a far more elegant solution using minimal javascript to just toggle the class "noscroll" on your body when you have a popup or div that you'd like to scroll (and not "overscroll" the whole page body).

while desktop browsers observe overflow:hidden -- iOS seems to ignore that unless you set the position to fixed... which causes the whole page to be a strange width, so you have to set the position and width manually as well. use this css:

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

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

I've work a little workarround without jquery. Not perfert but works fine (especially if you have a scroll-x in a scoll-y) https://github.com/pinadesign/overscroll/

Fell free to participate and improve it


This solution doesn't require you to put a scrollable class on all your scrollable divs so is more general. Scrolling is allowed on all elements which are, or are children of, INPUT elements contenteditables and overflow scroll or autos.

I use a custom selector and I also cache the result of the check in the element to improve performance. No need to check the same element every time. This may have a few issues as only just written but thought I'd share.

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

While disabling all "touchmove" events might seem like a good idea, as soon as you need other scrollable elements on the page it will cause problems. On top of that, if you only disable "touchmove" events on certain elements (e.g. body if you want the page to be non-scrollable), as soon as it is enabled anywhere else, IOS will cause unstoppable propagation in Chrome when the URL bar toggles.

While I cannot explain this behavior, it looks like the only way to prevent seems to set the body's position to fixed. The only problem doing is that you will lose the position of the document - this is especially annoying in modals for example. One way to solve it would be to use these simple VanillaJS functions:

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

Using this solution you can have a fixed document and any other element in your page can overflow by using simple CSS (e.g., overflow: scroll;). No need for special classes or anything else.


Here's a zepto compatible solution

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

this one works for me (plain 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>

Try this It'll work perfect.

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

I had surprising luck with with simple:

body {
    height: 100vh;
}

It works great to disable overscroll for pop-ups or menus and it doesn't force browser bars to appear like when using position:fixed. BUT - you need to save scroll position before setting fixed height and restore it when hiding the pop-up, otherwise, browser will scroll to top.

참고URL : https://stackoverflow.com/questions/10238084/ios-safari-how-to-disable-overscroll-but-allow-scrollable-divs-to-scroll-norma

반응형