iOS Safari –オーバースクロールを無効にして、スクロール可能なdivに通常のスクロールを許可する方法


100

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


最後の1つも試してみましたが、どちらもうまくいきませんでした
サンティアゴレベラ

スクロール可能なdivの親でスクロールイベントを明示的にキャプチャし、実際にスクロールできないようにすることで、divの終わりを超えてスクロールするときにビューポートが移動しないようにすることができました。jquery mobileを使用している場合、次のようにページレベルでこれを行うのが理にかなっています:$( 'div [data-role = "page"]')。on( 'scroll'、function(e){e.preventDefault ();});
クリストファージョンソン


この問題を修正するこのスクリプトを見つけました!:) github.com/lazd/iNoBounce
JanŠafránek15年

投稿の上に誰かが7か月前に投稿した場合、リンクを再度投稿するのはなぜですか?
デニー

回答:


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の初心者で、簡単なコードでこれらのハンドラーをどこかで削除したい場合は、これらの2行がうまく機能します。 $(document).off('touchmove'); さらに $('body').off('touchmove touchstart', '.scrollable');
Devin 2013

それは私には完璧に働きました。どうもありがとうございました。
marcgg 2013年

1
これは、divにスクロールするのに十分なコンテンツがない場合は機能しません。誰かが別の質問をして、ここで答えました:stackoverflow.com/q/16437182/40352
Chris

複数の「.scrollable」クラスを許可するにはどうすればよいですか?1つでも問題なく動作しますが、別のdivもスクロール可能にする必要があります。ありがとう!
MeV

23

タイラーダッジの優れた答えを使用すると、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
後方にドラッグするのに最適ですが、下にドラッグしてもサファリは移動します。
アバダバ2013

1
素晴らしいソリューション...どうもありがとうございました:)
アーミルシャー14

これでうまくいきました。ありがとう!この問題を解決するために1.5日以上費やしています。
Achintha Samindika 2014年

これは素晴らしく、うまく機能し、解決策を模索するためのストレスをさらに軽減しました。クバありがとう!
Leonard

12

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

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

7

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

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

3
おかげで...これはように思えるはずです動作しますが、それはしていません。また、「。scrollable」(ドット付き)ではなく「scrollable」にしないでください。
ジェフ

1
それはタッチイベントを受け取る最も深くネストされた要素のようですので、スクロール可能なdivにいるかどうかを確認するためにすべての親をチェックする必要があるかもしれません。
クリストファージョンソン

3
jQueryが使用されている場合、なぜdocument.body.addEventListenerを使用するのですか?それは理由がありますか?
fnagel 14

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.pageYではなくe.originalEvent.touches [0] .pageYを確認する必要がありました。これは機能しましたが、すでにスクロールdivの最後にいる場合に限ります。スクロールが進行中(たとえば、非常に高速にスクロールした場合)は、スクロール可能なdivの終わりに達しても停止しません。
キーンセージ2013

4

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

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

デスクトップブラウザーはオーバーフローを監視しますが、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();
});

これは非常に役に立ち、最小限のアプローチで、私が必要とするものだけです。top:0で位置を固定に設定します。左:0; 幅:100%; 私が欠けていた要素でした。これはフライアウトメニューにも役立ちます。
bdanin

3

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

参加して改善してください


1
ジェフと同じ問題があり、すべての答えを試しましたが、あなたの答えはうまくいきました。ありがとうございました!
ドミニクシュライバー2013

受け入れられた回答は、.scrollableのdivにオーバーフローを引き起こすのに十分なコンテンツがある場合にのみ機能しました。オーバーフローしなかった場合でも、「バウンス」効果はまだ存在していました。しかし、これは完全に機能します、ありがとう!
アダムマーシャル

1

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

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

$.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"イベントのみを無効にすると、他の場所で有効にされるとすぐに、URLのときにIOSが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;。特別なクラスなどは必要ありません。


0

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

これは私のために動作します(プレーン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.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

私はシンプルで驚くべき運がありました:

body {
    height: 100vh;
}

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.