見出し画像

フルページスクロールの実装について - (2)JSを作成しスタイルを調整

前回、フルページスクロールの準備として、全画面表示のページを作成。今回は、前回のHTMLの構成そのままに、クラスを付与するだけでフルページスクロールが実現できるような実装を施そうと思う。

要件

  1. フルページで遷移したい(fullPage.js の様な挙動をさせたい)

  2. スマホのスクロールでアドレスバーが隠れて画面の高さが変わってしまう問題を対応する

  3. 画面内に収まらないコンテンツはスクロールして全体を表示できるようにする
    (次のコンテンツが表示されるまでは自動的にスクロールさせない)

  4. フッターまできたら普通にスクロール

  5. PC表示では1画面2分割されているコンテンツが、スマホ時は2画面となり、各画面フルページスクロールで遷移する

  6. 一通りページのコーディングが終わせて、最後にクラスを付与するだけでフルページスクロールが実現できるようにしたい

今回の対応内容

  • フルスクロール対象コンテンツにクラスを付与し、対象コンテンツを取得

  • フルスクロール対象コンテンツが新たに画面内に入ってきたら(observerで監視)、そのコンテンツにスクロールする

  • スムーススクロールアニメーションを作成

  • 念のためスクロール中は操作を受け付けない

  • fullPage.js の様に、アドレスバーが隠れにくいようにスタイルを調整

①フルスクロール対象コンテンツにクラス付与

前回作成したHTMLにて、全画面表示のコンテンツとして作成した .p-section 要素すべてに対し、フルスクロール対象コンテンツであることを示すクラス .js-full を付与する。

index.html (main内)
---

:

<main class="l-main">
<section class="p-section js-full">
<h2>要件</h2>
<ol>
<li>フルページで遷移する(fullpage.jsの様な挙動をさせたい)</li>
<li>スマホのスクロールでアドレスバーが隠れて画面の高さが変わってしまう問題を対応する</li>
<li>1画面内に収まらないコンテンツはスクロールして全体を表示できるようにする<br>(次のコンテンツが表示されるまでは自動的にスクロールさせない)</li>
<li>フッターまできたら普通にスクロール</li>
<li>PC表示では1画面2分割されているコンテンツが、スマホ時は2画面フルページで遷移する</li>
<li>一通りコーディングが終わってからクラスを付与するだけでフルページスクロールできるようにしたい</li>
</ol>
</section>

<section class="p-section bg-gray js-full">
<h2>1. フルページスクロール対象のコンテンツにクラス付与</h2>
<p>とりあえずシンプルに1コンテンツ1セクションとしてマークアップ。</p>
</section>

<section class="p-section js-full">
<h2>2. 1コンテンツが全画面表示となるようスタイル作成</h2>
<p><code>.p-section</code>に<code>height: 100vh;</code>を設定。</p>
<p>要件2. の対応について、今回はスマホによる表示時(※2)<code>.p-section</code>に<code>padding-bottom: 72px;</code>を設定。</p>
<p>※ <code>height: 100svh;</code>はFirefoxが非対応のため使わない。<br>【参考】<a href="https://caniuse.com/?search=svh" target="_blank">svh | Can I use</a></p>
<p>※2 スマホ表示時htmlタグ等に<code>.ua-mobile</code>を付与するUA判定処理をJSで追加予定</p>
</section>

<div class="p-column">
<section class="p-section bg-gray js-full">
<h2>3. 要件3. の対応</h2>
<p>1画面2分割されたコンテンツが、スマホ表示では2画面コンテンツになる。</p>
</section>

<section class="p-section bg-gray2 js-full">
<h2>スマホ時には全画面コンテンツとなる</h2>
<p>ここのコンテンツが、PCでは右側に表示され、スマホでは次の全画面コンテンツになる。</p>
</section>
</div>
</main>

:

②.js-full 要素をすべて取得する

js/app.js
---

document.addEventListener('DOMContentLoaded', () => {

  :

  // フルページスクロール準備
  const sections = document.querySelectorAll('.js-full');
  const arraySections = Array.from(sections);

  :

});

③フルページ対象コンテンツを監視

IntersectionObserver を使用し、7.5%程度(大体このくらいの数値が落ち着く)画面(ビューポート)内に入ってきたらスクロールするよう監視する。

js/app.js
---

document.addEventListener('DOMContentLoaded', () => {

  :

  // フルページスクロール対象コンテンツ取得
  const sections = document.querySelectorAll('.js-full');
  const arraySections = Array.from(sections);

  // フルページスクロール対象コンテンツ監視処理
  const observer = new IntersectionObserver((entries) => {

   // ここに監視処理(⑤の内容)を追加

  }, {
    threshold: 0.075,
  });

  arraySections.forEach((section) => {
    observer.observe(section);
  });

:

});

④スムーススクロールアニメーション作成

ターゲットの位置までイージングをかけたスムーススクロールアニメーション。スクロール要素、移動先の位置、時間の設定のみのシンプルなもの。

js/app.js
---

document.addEventListener('DOMContentLoaded', () => {

  :

  // スムーススクロールアニメーション
  const smoothScrollTo = (element, target, duration) => {
    const start = element.scrollTop;
    const change = target - start;
    const startTime = performance.now();

    const animateScroll = currentTime => {
      const timeElapsed = currentTime - startTime;
      const progress = timeElapsed / duration;
      const ease = progress < 0.5
        ? 2 * progress * progress
        : -1 + (4 - 2 * progress) * progress;

      element.scrollTop = start + change * ease;

      if (timeElapsed < duration) {
        requestAnimationFrame(animateScroll);
      } else {
        element.scrollTop = target;
      }
    };

    requestAnimationFrame(animateScroll);
  };

  :

});

⑤フルページスクロール対象コンテンツが画面内に入ってきたらスクロールさせる

IntersectionObserver で監視中のコンテンツが画面内に入ってきた場合、そのコンテンツの offsetTop の値を取得し、④で作成したアニメーションを使用し offsetTop の位置まで移動させる。

js/app.js
---

document.addEventListener('DOMContentLoaded', () => {

  :

  // フルページスクロール対象コンテンツ取得
  const sections = document.querySelectorAll('.js-full');
  const arraySections = Array.from(sections);

  // フルページスクロール対象コンテンツ監視処理
  const observer = new IntersectionObserver((entries) => {

    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const currentSection = entry.target;
        const entryTop = currentSection.offsetTop;
        smoothScrollTo(wrapper, entryTop, 750);
      }
    });

  }, {
    threshold: 0.075,
  });

  arraySections.forEach((section) => {
    observer.observe(section);
  });

:

});

⑥念のためスクロール中は操作無効に

スムーススクロール中の操作で誤作動を起こさないように、念のため操作を受け付けない処理を追加する。内容は簡単に書くと次のとおり。

  • スムーススクロール中は body タグに .is-scrolling を付与

  • スムーススクロール完了後、body タグの .is-scrolling を削除

  • body タグに .is-scrolling が付与されている間は、マウスホイール・スワイプ・キー操作を無効

js/app.js
---

document.addEventListener('DOMContentLoaded', () => {

  :

  // フルページスクロール対象コンテンツ取得
  const sections = document.querySelectorAll('.js-full');
  const arraySections = Array.from(sections);

  // フルページスクロール対象コンテンツ監視処理
  const observer = new IntersectionObserver((entries) => {

    // スクロール中は操作無効
    if (body.classList.contains('is-scrolling')) {
      window.removeEventListener('wheel', (e) => e.preventDefault());
      window.removeEventListener('touchstart', (e) => e.preventDefault());
      window.removeEventListener('touchmove', (e) => e.preventDefault());
      window.removeEventListener('keydown', (e) => e.preventDefault());
    }

    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        body.classList.add('is-scrolling'); // 操作無効開始
        const currentSection = entry.target;
        const entryTop = currentSection.offsetTop;
        smoothScrollTo(wrapper, entryTop, 750);
      }
      setTimeout(() => { // スムーズスクロール完了後に操作無効解除
        body.classList.remove('is-scrolling');
      }, 750);
    });
  }, {
    threshold: 0.075,
  });

  arraySections.forEach((section) => {
    observer.observe(section);
  });

:

});

以上で JavaScript の準備完了。

⑦アドレスバーが隠れにくくなるようスタイル調整

fullPage.js のデモ等をスマホで確認すると、スクロールしてもアドレスバーが表示されたままになっていることが分かる。これはつまり、bodyではなく要素内でスクロールさせている、ということ。(アドレスバーが隠れるのは下方向へのbodyスクロールを感知した場合)

ということで、最後に bodyスクロールしづらい状況をスタイルで作る。
また、スタイルでもスムーススクロール中はスマホのタッチアクションを無効にするスタイルを追加しておく。

※scss形式で記載しています。一部ブラウザでは効かないため、CSSとして利用する場合は下部に用意したサンプルコードをご確認ください。

css/style.css(scss形式で記載)
---

:

body {
    height: 100vh;
    overflow-y: hidden;

    &.is-scrolling {
        touch-action: none;
    }
}

:

まず、body タグに overflow-y: hidden; を設定する。
また、.is-scrolling 時は touch-action: none; を設定し、スタイル上でもタッチ操作を無効にしておく。(誤作動防止のためスワイプ以外の操作も行わせないように)

css/style.css
---

:

/* .l-wrapper */
.l-wrapper {
    overflow-y: scroll;
    position: relative;
    min-height: 100vh;
    height: 100%;
    padding: 1px;
}

:

次に、.l-wrapper 要素内でスクロールをさせたいので、overflow-y: scroll; を設定し、height: 100%; と、スマホでスクロールしても bodyスクロールを発生しづらくさせるhackとして、padding: 1px; を設定する(これが超重要)

まとめ

以上でフルページスクロールの実装は完了となる。かなりシンプルに実現できたのではないかと思う。

observerがMacのsafariでも割と安定して動作するようになってきたので、フルページスクロールのようなコンテンツを作るのが大分楽になった。

ただ、スマホで下方向にスクロールするとアドレスバーが隠れる、という仕様が一般化してきたことにより、100vhの使い勝手がとても悪い。

そこで、今回の様にスマホ表示時は padding-bottom: 72px; を設定しコンテンツ内容の下の部分が隠れない様にある程度割り切って設計し、スクロール要素である .l-wrapper に padding: 1px; を入れることで、アドレスバーが隠れにくい状況を作成してみた。

なお、もしコンテンツ内で position: absolute; による bottom配置の要素がある場合、スマホ表示時はbottom位置 + 72px を設定する必要がある。

今回のサンプル


この記事が気に入ったらサポートをしてみませんか?