web-dev-qa-db-ja.com

iOS Safari –オーバースクロールを無効にするが、スクロール可能なdivを通常にスクロールさせる方法は?

私はiPadベースのWebアプリに取り組んでいますが、オーバースクロールを防止して、Webページのように見えないようにする必要があります。現在、これを使用してビューポートをフリーズし、オーバースクロールを無効にしています:

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の先頭または末尾を超えてスクロールすると、ビューポートは引き続き移動します。私もそれを無効にする方法を見つけたいです。

97
Jeff

これにより、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にオーバーフローがないときにページ全体のスクロールをブロックする場合、これは機能しないことに注意してください。それをブロックするには、すぐ上のイベントハンドラーではなく、次のイベントハンドラーを使用します( this question から適応)。

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

Tyler Dodgeの優れた answer を使用して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);
}
23
Kuba Holuj

まず、通常どおりにドキュメント全体でデフォルトのアクションを防止します。

$(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" />
12
Jonathan Tonge

オーバースクロール無効化コードにもう少しロジックを追加して、問題のターゲット要素がスクロールしたい要素ではないことを確認できますか?このようなもの:

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

これに対する最善の解決策は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>
6
Elias Fyksen

上にスクロールしようとするときにスクロール可能な要素がすでに上にスクロールしているか、下にスクロールしようとして下にスクロールしていて、ページ全体の移動を停止するデフォルトアクションを防止しているかどうかを確認します。

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

私は、スクロール可能な領域(カートのスクロール可能なビューを持つ「ショッピングカート」のポップダウン)があるポップアップがある場合に、すべてのボディスクロールを防ぐ方法を探していました。

スクロールしたいポップアップまたはdivがあるときに(ページ全体を「オーバースクロール」しないで)ボディのクラス「noscroll」を切り替えるために、最小限のjavascriptを使用してはるかにエレガントなソリューションを作成しました。

デスクトップブラウザは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();
});
4
nrutas

私はjqueryなしで少しの回避策を働いています。適切ではありませんが、正常に動作します(特にscoll-yにscroll-xがある場合) https://github.com/pinadesign/overscroll/

自由に参加して改善してください

3
PiñaDesign

すべての「touchmove」イベントを無効にすることは良い考えのように思えますが、ページ上で他のスクロール可能な要素が必要になるとすぐに問題が発生します。さらに、特定の要素で「touchmove」イベント(ページをスクロールできないようにする場合はbody)のみを無効にすると、他の場所で有効になるとすぐに、IOSは停止できなくなりますURLバーが切り替わるときのChromeの伝播。

この動作を説明することはできませんが、防止する唯一の方法は、ボディの位置をfixedに設定しているようです。唯一の問題は、ドキュメントの位置を失うことです-これは、たとえば、モーダルで特に迷惑です。これを解決する1つの方法は、次の単純な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;など)を使用してオーバーフローできます。特別なクラスなどは必要ありません。

1

このソリューションでは、すべてのスクロール可能なdivにスクロール可能なクラスを配置する必要はないため、より一般的です。スクロールは、INPUT要素のcontenteditableであるか、その要素の子であるすべての要素、およびオーバーフロースクロールまたは自動で許可されます。

カスタムセレクターを使用し、チェックの結果も要素にキャッシュしてパフォーマンスを向上させます。毎回同じ要素をチェックする必要はありません。これには、書かれただけで共有できると思ったいくつかの問題があるかもしれません。

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

これは私のために働いています(プレーン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
RJS

これが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

これを試してみてくださいそれは完璧に動作します。

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

シンプルで驚くほど幸運でした:

body {
    height: 100vh;
}

ポップアップまたはメニューのオーバースクロールを無効にするとうまく機能し、position:fixedを使用する場合のようにブラウザーバーを強制的に表示しません。ただし、固定の高さを設定する前にスクロール位置を保存し、ポップアップを非表示にするときに元に戻す必要があります。そうしないと、ブラウザーが上部にスクロールします。

0