【JavaScript】指定した位置で慣性スクロールを止めよう【スマホ】

どうも、Reoです。

スマホ等のタッチデバイスでスクロール時に、指定した位置でスクロールをストップさせたい!というのを実装してみたので紹介します。

PureでVanillaな素のjsで実装しました。

 

今回デモ用に作ったのは以下のものです。

指定したボックスの下端が画面の中心に来たらスクロールが止まります。

スクロールを止める方法

スクロールを止める方法は結構色々試しました。

試した方法は3種類。

  1. addEventListenerでスクロール中にpreventDefaultをする
  2. スクロール中に画面をタッチしたことにして止める
  3. 指定座標についたらscrollToで同じ座標にスクロールさせる

1はtouchmoveを指定してやってみましたが、慣性スクロールは止まらず、慣性スクロールが止まった後のtouchmoveを無効にすることしかできませんでした。

2の擬似タッチはタッチしたことにならず諦め。原理的にはイケそうな気はしてます。

 

そして今回の方法(3番)は発想の転換、というかそんな感じで偶然発見できた方法です。なにより、一番コード量が少なく簡単にできます!

スクロールを止める部分のコードはこちら。

/**
 * スクロールを止める
 */
function stopScroll() {
    // 現在のスクロール量
    var scrollOffsetY = window.pageYOffset;
    window.scrollTo(0,scrollOffsetY);
}

 

実質たったの2行で実現できちゃいました。
あとは位置指定部分と、スクロール中に呼ばれる部分を実装すればおkです。

要素が指定した範囲内にあるかの判別

要素(item)が指定範囲内にあるかどうかを返す関数を書きます。

function isItemInScreen( item ) {
    var screenHeight = window.innerHeight; // 画面の高さ
    var itemBottomY = item.getBoundingClientRect().bottom; // item bottom座標
    var boundary = screenHeight / 2; // 上からスクロールしてくるとここで止まる 画面topからどれだけか

    if ( 0 <= itemBottomY && itemBottomY <= boundary ) {
        return true;
    }

    return false;
}

今回は画面トップ(0)から画面の真ん中までの範囲内に要素があれば、trueが返るものを用意しました。

getBoundingClientRect()で要素のページ内座標が取得できます。

ここはお好みで止めたい位置を指定ください〜。

 

スクロール中の処理を書く

最後にスクロール中の処理を書きます。window.onscrollを使います。
 

/**
 * Scroll
 */
window.onscroll = scrollWindow;

/**
 * スクロールされている間呼ばれる
 */
function scrollWindow() {
    // 要素を取得
    var items = document.querySelectorAll('.stop');
    items.forEach( function(item){
        var backgroundColor = item.style.backgroundColor;
        if ( isItemInScreen(item) && !(backgroundColor == 'rgb(37, 37, 37)') ){
            item.style.backgroundColor = 'rgb(37, 37, 37)';
            stopScroll();
        }
    });
}

scrollWindow内にて要素を取得し、アイテムが範囲内にあるかどうかをチェックし、あればスクロールを止めます。

今回のデモでは、止まるのは最初の一度だけです。止まる時に色を変えて、その色で条件分岐しています。本来ならもうちょっと別のことで条件分岐したほうがいいと思います。

 

はい、これだけ!

全体コード

gistにコードあげときました。一応htmlとcssも付けておきました。

実はjs単体の記事は初めてなので、CodePen的なのは何も用意してないです。あとで追記するかもしれないし、しないかもしれないです。

慣性スクロールを止めるのは使いどころは結構難しいような、でも意外とあるような、そんな気がしてます。
例えばヘッダーとかフッターでスクロールoffsetがマイナスになるのを防ぐとか。

onScrollが必ず境界値で呼ばれるわけではないのでスクロール速度が速いとずれることがあります。

 

最近気付いたけれど、Xcodeのシミュレータならローカルファイルでもチェックできるので便利ですね。

 

ちなみに慣性スクロールはタッチデバイスでのみなので、同様なものをPC側で実装するには、addEventListenerで一定時間preventDefaultで実現することができます。動かしてみるとちょっと微妙だけれど。

これはjavascript – How to disable scrolling temporarily? – Stack Overflowの回答で実装できます。

 

書き終わってプレビューして見たらGIFが酷いね。。。雰囲気が伝われば・・・。

ほいでは〜〜〜。

Comments...

2021/08/23 06:08

こんにちは。
大変参考になる記事、ありがとうございます。

ただ、今回のコードでは止まるのは最初の一度だけですが、
itemBottomYを超えたら「下にスクロールできない」「上にスクロールはできる」
を実現するにはどうしたら良いでしょうか?

ご教授いただけたら幸いです。
よろしくお願いいたします。

    2021/08/23 11:08

    初めまして、コメントありがとうございます。

    本記事の方法ですと、「下にスクロールできない」かつ「上にスクロールはできる」を実現するのは難しいかと思います。
    あくまで、慣性スクロール(指を離している状態でのスクロール動作)を一時的に止めるものになりますので...。

    また、私自身JavaScriptはあまり詳しくはなくて申し訳ないのですが、私の調べた限りでは実現はかなり難しいのではないかな?と思います。

    まず実装の条件として以下があります。


    スクロール中に指定場所を超えているかどうかを判定する(itemBottomY を超えているかどうか)
    超えている場合はスクロールを無効にする
    1, 2の状態で上にスクロールをする場合にのみスクロールを有効化する


    この1~3の条件を判定する場所は window.onscroll の中になります。

    2の時点でスクロールを無効化する方法として
    document.addEventListener('touchmove', function(e) {e.preventDefault();}, {passive: false});
    のように touchmove 自体を無効化にします。

    そうすると、2の時点でスクロールは行われなくなってしまうので、3を判定する方法がなくなってしまいます。
    「上にスクロールする」も「下にスクロールする」のどちらも受け付けない状態になっています。

    このように一度スクロール無効をしてしまうと、解除するきっかけが作れないので、本件は実現が難しいのではないかなと思います。

    この他にももしかすると方法はあるのかも知れませんが、私の知っている限りではこのような回答になってしまいます。
    お力になれず、申し訳ございません。

    アバター
    from.Reo(管理人)
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

【pure js】アイキャッチの下などにあるヘッダーをスクロール後固定する

pureでvanillaな素のものjsで。

[ アイキャッチ画像 ]

[ ヘッダー ]

[ コンテンツ ]

みたいな作りのwebページでスクロール後ヘッダーを固定する。

固定解除部分は 元のcssにあわせて書けばおkです。

 

JavaScript

iOSでiframeのwidthの挙動がおかしくなる時の対処メモ【pure js】

pureでvanillaな素のjsで。

どういう挙動かというと

iframeロード → 表示(多分画面幅くらい)→ ちょっと縮小 → さらに縮小して目的のwidthになる

という感じ。画像は載せられないので割愛します。

指定したwidthからはみ出た状態で始まり、最後には指定したwidthになるといった挙動ですね。

結局

var iframe = document.createElement('iframe');
iframe.width = '100%';
iframe.height = '100%';

とかいう風に書いているところに

iframe.scrolling = 'no';

この一文を入れてやると解決しました。

viewportとかwidthのこといっぱい調べたのになんのことはない・・・。

JavaScript

【vagrant+CentOS+CakePHP】JavaScriptファイルで何書いてもillegal characterって言われたときメモ..

jsファイルに何かいてもillegal characterって言われて泣くかと思った。(ブラウザはFirefox)

vagrantで下の記事通りに設定したらちゃんとjs動くようになった。本当に助かった・・・。

JavaScript
more