モバイルのスクロール全体・一部禁止(Pull-to-refresh)のコンポーネントを作ってみた(React)

<h2>はじめに</h2>
モバイル用のwebアプリを作成していまして、
リロードされると色々と困ると思ったのでせめてスクロールリロードは禁止にしようと思い作りました。<br/>
その時に画面全部のスクロール禁止は簡単だけど、一部分のスクロール許可は大変だなと思ったのでメモです。<br/>
※一部分のスクロール許可コンポーネントはコンボボックスには対応していません。
<h2>完成したコンポーネント・CSS</h2>
画面全部のスクロール禁止処理コンポーネント<br/>

```react:AllScrollLock.jsx
import React, { useEffect, useCallback } from "react";

const AllScrollLock= React.memo(() => {
  /**
   * イベントリスナーの設定
   */
  useEffect(() => {
    // モバイルスクロール禁止処理
    document.addEventListener("touchmove", scrollNo, { passive: false });

    return () => {
      // イベントの設定解除
      document.removeEventListener("touchmove", scrollNo);
    };
  }, []);

  /**
   * モバイルスクロール禁止処理
   */
  const scrollNo = useCallback((e) => {
    e.preventDefault();
  }, []);

  return <></>;
});

export default AllScrollLock;
````
一部分スクロール許可のコンポーネント
```react:AllowPartialScrolling.jsx
import React, { useEffect, useRef, useCallback } from "react";

const AllowPartialScrolling= React.memo((props) => {
  // スクロール可能DOM格納
  const scrollArea = useRef(null);

  useEffect(() => {
    scrollArea.current = document.getElementById("scroll_item");
    scrollArea.current.scrollTop = 1;

    // モバイルスクロール禁止処理
    window.addEventListener("resize", setFillHeight);
    window.addEventListener("touchmove", scrollNo, { passive: false });
    scrollArea.current.addEventListener("focusout", onFocusOut);
    scrollArea.current.addEventListener("scroll", scrollControl);
    scrollArea.current.addEventListener("touchstart", touchHandler, {
      passive: false,
    });

    return () => {
      // イベントの設定解除
      window.removeEventListener("resize", setFillHeight);
      window.removeEventListener("touchmove", scrollNo);
      scrollArea.current.removeEventListener("focusout", onFocusOut);
      scrollArea.current.removeEventListener("scroll", scrollControl);
      scrollArea.current.removeEventListener("touchstart", touchHandler);
    };
  }, []);

  /**
   * 高さを調整する処理
   */
  const setFillHeight = useCallback(() => {
    window.scroll(0, 0);
  }, []);

  /**
   * モバイルスクロール禁止処理
   */
  const scrollNo = useCallback((e) => {
    if (
      e.target.closest("#scroll_item") === scrollArea.current &&
      scrollArea.current.scrollTop !== 0 &&
      scrollArea.current.scrollTop + scrollArea.current.clientHeight !==
        scrollArea.current.scrollHeight
    ) {
      e.stopPropagation();
    } else {
      e.preventDefault();
    }
  }, []);
  /**
   * ピンチズームをしない処理(2本指で操作させないようにする)
   */
  const touchHandler = useCallback((event) => {
    if (event.touches.length > 1) {
      event.preventDefault();
    }
  }, []);

  /**
   * フォーカスアウトした時の処理
   */
  const onFocusOut = useCallback(() => {
    window.scroll(0, 0);
  }, []);
  /**
   * スクロールリロードさせない処理
   */
  const scrollControl = useCallback(() => {
    if (scrollArea.current.scrollTop === 0) {
      scrollArea.current.scrollTop = 1;
    } else if (
      scrollArea.current.scrollTop + scrollArea.current.clientHeight ===
      scrollArea.current.scrollHeight
    ) {
      scrollArea.current.scrollTop = scrollArea.current.scrollTop - 1;
    }
  }, []);


  return (
    <div className="scroll_control" id="scroll_item">
      <div className="scroll_children">{props.children}</div>
    </div>
  );
});

export default AllowPartialScrolling;
```
一部分スクロール許可のcss
```css:scroll.css
/* モバイルスクロールローディング禁止 */
.scroll_control {
  overflow-y: scroll;
  height: 100vh;
  -webkit-overflow-scrolling: touch;
  padding: 1.5vh 0 24vh 0;
}

.scroll_children {
  min-height: 100vh;
  -webkit-overflow-scrolling: touch;
  overflow-y: scroll;
}
```
<h2>実装方法</h2>
<ul>
 <li>画面全体をスクロールする必要がない画面は画面全部のスクロール禁止処理コンポーネントをimportします。</li> 
<li>画面の一部分をだけスクロールしたい画面は一部分スクロール許可のコンポーネントをimportします。</li>
</ul>
例：画面全体スクロール禁止の場合<br/>

```react:app.jsx
import React from 'react';
import AllScrollLock from "../components/test/AllScrollLock";
import Hoge1 from "../components/hoges/Hoge1";
import Hoge2 from "../components/hoges/Hoge2";
import Hoge3 from "../components/hoges/Hoge3";
// フォルダの階層は各々違うの書き換えお願いします。

export default function App(props) {
    return (
     <>
      <Hoge1 />
      <Hoge2 />
      <Hoge3 />
      <AllScrollLock/>
    </>
  );
}
```

使いたい所で置くだけで画面全部がスクロール禁止になります。<br/>
例：一部分のスクロール可能コンポーネント<br/>

```react:app.jsx
import React from 'react';
import AllowPartialScrolling from "../components/test/AllowPartialScrolling";
import Hoge1 from "../components/hoges/Hoge1";
import Hoge2 from "../components/hoges/Hoge2";
import Hoge3 from "../components/hoges/Hoge3";
// フォルダの階層は各々違うの書き換えお願いします。

export default function App(props) {
    return (
     <>
     <AllowPartialScrolling>
       <Hoge1 />
       <Hoge2 />
       <Hoge3 />
     </AllowPartialScrolling>
    </>
  );
}
```
一部分許可の場合はスクロールしたいDOMをはさむとスクロール可能になります。<br/>

<h2>大変だった部分：一部分のスクロール可能コンポーネント</h2>
<ul>
<li>テキストボックス押下時にキーボード出現のため画面の高さが変更される部分の対応。</li>
<li>cssの上下余白調整</li>
</ul>
<h2>感想</h2>
ページ事にスクロールの許可ができるよになったのでよかったです。作成している時はパッケージがないのかと思いましたが、パッケージありましたね（笑）<br/>
作成後に知りました:joy:<br/>

記事：[body-scroll-lock.js「すべてのブラウザでスクロールのロックしたるで」←こいつ超有能](https://qiita.com/Naughty1029/items/a3e20a47094f6eb7bf7b)

※追記↑cssでスクロールできる場所を作成しなければいけない点は一緒でした。
<h2>参考にしたwebサイト</h2>
<ul>
<li>

 [iOS でページ全体はスクロールを無効にし、個別の要素（textarea など）では有効にする方法](https://qiita.com/noraworld/items/2834f2e6f064e6f6d41a)
</li>

<li>

 [2019年、JavaScriptでのスクロール一時禁止はこれだ！（スマートフォン）](https://qiita.com/yukiTTT/items/773356c2483b96c9d4e0)
</li>
<li>

 [一筋の光が差し込むWebサイト](https://qiita.com/de_teiu_tkg/items/3bdb64168978ee877090)
</li>
参考になりました。有難うございます:bow:

</ul>