LoginSignup
5
4

More than 3 years have passed since last update.

Reactでライブラリなしで、スムーズにスクロールしてみたサンプル

Posted at

Reactを使用して、ボタンを押したときに指定のDOMの位置までスムーズにアニメーションしながらスクロールするサンプルを作成してみました

スムーズにスクロールさせる方法は色々あると思いますが、私は以下の方法を試してみました。

  • 方法1 scrollTo() を使う (※IEとSafarではうまく動かなかった)
  • 方法2 requestAnimationFrame を使う

scrollTo()

scrollTo()を呼び出すときにオプションでbehavior: 'smooth'と指定することでスムーズにアニメーションしながらスクロースしてくれました。
※IEとSafariではスクロールするけど、アニメーションしないで、一瞬で対象の位置まで行ってしまいました

See the Pen react smooth scroll by 奥村健吾 (@okumurakengo) on CodePen.

const styles = {
  width: '500px',
  height: '500px',
};

function App() {
  const div1 = React.useRef()
  const div2 = React.useRef()
  const div3 = React.useRef()

  /**
   * @param {HTMLElement} target 
   */
  function smoothScroll(target) {
    const { top } = target.getBoundingClientRect()
    window.scrollTo({
      top: top + window.pageYOffset,
      behavior: "smooth"
    });
  }

  return (
    <div>
      <div ref={div1} style={{ background: 'yellowgreen', ...styles }}>
        <p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
        <p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
        <p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
      </div>

      <div ref={div2} style={{ background: 'seagreen', ...styles }}>
        <p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
        <p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
        <p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
      </div>

      <div ref={div3} style={{ background: 'skyblue', ...styles }}>
        <p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
        <p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
        <p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
      </div>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));

requestAnimationFrame

スクロール位置の判定は自分でしないといけないですが、以下のようにするとスムーズにアニメーションしながらスクロースしてくれました、
私の環境ではIE、Safariでも動作するのを確認できました。

See the Pen react smooth scroll (requestAnimationFrame) by 奥村健吾 (@okumurakengo) on CodePen.

/**
 * 下にスクロールするピクセル値の間隔
 * @constant
 * @type {number}
 */
const INTERVAL = 15;

const styles = {
  width: '500px',
  height: '500px',
};

function App() {
  const requestRef = React.useRef()
  const targetTopRef = React.useRef()
  const startYOffset = React.useRef()

  const div1 = React.useRef()
  const div2 = React.useRef()
  const div3 = React.useRef()

  /**
   * @param {HTMLElement} target 
   * @returns {void}
   */
  function smoothScroll(target) {
    // スクロール対象の位置を取得
    ({ top: targetTopRef.current } = target.getBoundingClientRect())

    // スクロール開始位置を取得
    startYOffset.current = window.pageYOffset

    // アニメーション開始
    animate()
  }

  /**
   * @returns {void}
   */
  function animate() {
    requestRef.current = requestAnimationFrame(animate);

    if (targetTopRef.current > 0) {
      // 下にスクロールする場合
      if (((startYOffset.current + targetTopRef.current) - window.pageYOffset) < INTERVAL) {
        // 残りのスクロールするピクセル値が、INTERVALの値より少ない場合は
        // 行き過ぎないようにスクロール値を調整
        window.scrollTo(0, window.pageYOffset + ((startYOffset.current + targetTopRef.current) - window.pageYOffset))
      } else {
        // 下にスクロール
        window.scrollTo(0, window.pageYOffset + INTERVAL)
      }

      if (((startYOffset.current + targetTopRef.current) <= window.pageYOffset)
      || (document.documentElement.scrollTop + window.innerHeight) >= (document.documentElement.offsetHeight)) {
        // スクロールの対象まできたか、ウィンドウの最下部にきたらスクロール終了
        cancelAnimationFrame(requestRef.current);
      }
    } else {
      // 上にスクロールする場合
      if (-((startYOffset.current + targetTopRef.current) - window.pageYOffset) < INTERVAL) {
        // 残りのスクロールするピクセル値が、INTERVALの値より少ない場合は
        // 行き過ぎないようにスクロール値を調整
        window.scrollTo(0, window.pageYOffset + ((startYOffset.current + targetTopRef.current) - window.pageYOffset))
      } else {
        // 上にスクロール
        window.scrollTo(0, window.pageYOffset - INTERVAL)
      }

      if ((startYOffset.current + targetTopRef.current) >= window.pageYOffset) {
        // スクロールの対象まできたらスクロール終了
        cancelAnimationFrame(requestRef.current);
      }
    }
  }

  return (
    <div>
      <div ref={div1} style={{ background: 'yellowgreen', ...styles }}>
        <p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
        <p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
        <p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
      </div>

      <div ref={div2} style={{ background: 'seagreen', ...styles }}>
        <p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
        <p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
        <p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
      </div>

      <div ref={div3} style={{ background: 'skyblue', ...styles }}>
        <p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
        <p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
        <p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4