JavaScriptでトップへ戻るボタンを設置する方法

読者の悩み

  • JavaScriptでトップへ戻るボタンの設置方法がわからない
  • scrollイベントでブラウザに負担をかけたくない

本記事の内容

  • JavaScriptでトップへ戻るボタンを設置する方法
  • scrollイベントでブラウザの負荷を減らす方法
  • すべてのページでトップへ戻るボタン設置するカスタマイズ

トップへ戻るボタンを設置したいけど、うまくいかない!ん〜〜

JavaScriptのscrollイベントで設置出来るのですが、けっこう難しいですよね。僕もかなり時間かかりました。

理屈さえわかれば、簡単なのでトライしてみてください。

JavaScriptでトップへ戻るボタンを設置する方法

まず、どんな「トップへ戻るボタン」を設置するかですが、

始めにページが表示された時は消えていて、ある程度スクロールすると、右下に「トップへ戻るボタン」が現れて、スクロールをページのトップの方に戻すと「トップへ戻るボタン」が消えるという仕様のものを作っていきたいと思います。

完成予想イメージはこちら。

HTML部分を作っていきましょう

まずはベースのHTML部分を作っていきましょう。

まずはバック部分の<section>を設置していきます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>PageTop</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <section class="sec-blue"></section>
  <section class="sec-yellow"></section>
  <section class="sec-green"></section>
  
  <script src="index.js"></script>
</body>
</html>

このsectionに高さを与えて、縦長のページを作っていきます。

<head>タグ内にcssファイルの読み込みと</body>タグの上にjsファイルの読み込みをして各ファイルも作っておいてください。

次はトップに戻るボタンのHTMLを書いていきます。

<body>タグのすぐ下に追記していきます。

 <div class="pagetop">
    <a href="#" class="pagetop__link">
      <span class="pagetop__text">TOP</span>
    </a>
 </div>

このHTMLがトップへ戻るボタンになります。

index.html全体を観てみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>PageTop</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="pagetop">
    <a href="#" class="pagetop__link">
      <span class="pagetop__text">TOP</span>
    </a>
  </div>

  <section class="sec-blue"></section>
  <section class="sec-yellow"></section>
  <section class="sec-green"></section>
  
  <script src="index.js"></script>
</body>
</html>

このような感じでシンプルなHTMLですね。
以上でHTMLファイルは完成しました。

次は、CSSでスタイルを付けていきましょう。

CSSでスタイルを付けていきましょう

それでは、styles.cssファイルにCSSを書いていきます。

こちらのCSSを書いてください。

html {
  scroll-behavior: smooth; /* スムーススクロール */
}

body {
  margin: 0;
}

.sec-blue {
  background-color: rgb(0, 140, 255, .5);
  height: 800px;
}

.sec-yellow {
  background-color: rgba(244, 244, 11);
  height: 800px;
}

.sec-green {
  background-color: rgb(129, 243, 129, .5);
  height: 800px;
}

/* トップへ戻るボタン */
.pagetop {
  width: 64px;
  height: 64px;
  position: fixed;
  bottom: 8%;
  right: 2%;
  transition: opacity .3s ease, visibility .3s ease;
  opacity: 0; /* デフォルトでは表示を消します */
  visibility: hidden; /* デフォルトでは表示を消します */
}

.pagetop__link {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background-color: #ffffff;
  border-radius: 50%;
  text-align: center;
  font-size: 18px;
  font-size: bold;
  line-height: 1.3;
  color: #333;
  text-decoration: none;
}

.pagetop__text {
  padding-top: 15px;
  position: relative;
}

.pagetop__text::before {
  content: "〈";
  display: inline-block;
  position: absolute;
  top: -13px;
  left: 50%;
  transform: translateX(-50%) rotate(90deg);
}

.pagetop__text::after {
  content: "〈";
  display: inline-block;
  position: absolute;
  top: -7px;
  left: 50%;
  transform: translateX(-50%) rotate(90deg);
}

<section>のスタイルと、トップへ戻るボタンのスタイルを付けました。トップへ戻るボタンのスタイルも付けています。

CSSのポイント

トップへ戻るボタンのフェードアニメーションはopacityとvisibilityで動作させます。

visibilityはdisplayと同じような挙動ですが、visibilityはtransitionに付けることができますので、フェードイン、フェードアウトの挙動を再現できます(意外とvisibilityの使い方は知られてないです)。

トップへ戻るボタンは28行目で、position: fixedで表示位置に固定しています。親要素にposition: relativeをつけないと、表示画面部分が基準位置になります。

トップへ戻るボタンはデフォルトでは消えているのですが、32行目、33行目のスタイルを削除して一応、ブラウザを確認してみてください。

このように右下にトップへ戻るボタンが表示されていたらOKです。

それでは、32行目、33行目のopacityとvisibilityを戻して、再びボタンを非表示しにしてください。

次はTOPボタン出現後のスタイルを付けていきます。

styles.cssファイルの最後尾に追記してください。

/* TOPボタン出現後のスタイル */
.pagetop.fadeIn {
  opacity: 1; /* フェードインアニメーション */
  visibility: visible; /* ボタンを表示 */
}

ボタンに「.fadeIn」のクラスをJavaScriptで付加することで、トップへ戻るボタンを表示させたいと思います。

styles.cssの全体はこんな感じです。

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
}

.sec-blue {
  background-color: rgb(0, 140, 255, .5);
  height: 800px;
}

.sec-yellow {
  background-color: rgba(244, 244, 11);
  height: 800px;
}

.sec-green {
  background-color: rgb(129, 243, 129, .5);
  height: 800px;
}

/* トップへ戻るボタン */
.pagetop {
  width: 64px;
  height: 64px;
  position: fixed;
  bottom: 8%;
  right: 2%;
  transition: opacity .3s ease, visibility .3s ease;
  opacity: 0; /* デフォルトでは表示を消します */
  visibility: hidden; /* デフォルトでは表示を消します */
}

.pagetop__link {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background-color: #ffffff;
  border-radius: 50%;
  text-align: center;
  font-size: 18px;
  font-size: bold;
  line-height: 1.3;
  color: #333;
  text-decoration: none;
}

.pagetop__text {
  padding-top: 15px;
  position: relative;
}

.pagetop__text::before {
  content: "〈";
  display: inline-block;
  position: absolute;
  top: -13px;
  left: 50%;
  transform: translateX(-50%) rotate(90deg);
}

.pagetop__text::after {
  content: "〈";
  display: inline-block;
  position: absolute;
  top: -7px;
  left: 50%;
  transform: translateX(-50%) rotate(90deg);
}

/* TOPボタン出現後のスタイル */
.pagetop.fadeIn {
  opacity: 1; /* フェードインアニメーション */
  visibility: visible; /* ボタンを表示 */
}

styles.cssファイルはこれで完成になります。

それでは、次はJavaScriptを書いていきましょう。

JavaScriptを書いていきましょう

それでは、JavaScriptを書いていきましょう。

データを取得しよう

まず必要なデータを取得していきます。

  • トップへ戻るボタン
  • トップへ戻るボタンを表示する位置のsection
  • ビューポートの高さ(使用しているディスプレイの高さ)

まずは3つのデータを取得していきます。

index.jsに書いていきましょう。

 'use strict'; 

{
  // データ取得
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  console.log(pageTop);

  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  console.log(secGreen);

  // ビューポートの高さ
  const vh = window.innerHeight;
  console.log(vh);
}

とりあえず、この3つのデータを取得しました。

ブラウザをリロードしてコンソールを観ていきましょう。

このような感じで、表示されていればOKです。

690はビューポートの高さですね。デベロッパーツールを下に表示していると、表示領域が狭くなり短い数値がでますが気にしなくても大丈夫です。

次はイベントを付加してみましょう。

イベントを付加しよう。

このイベントの中では2つのデータを取得します。

  • スクロール量
  • 要素の位置までの相対数値

この2つの取得方法は、

スクロール量は「window.pageYOffset」 で取得できます。

要素の位置までの数値は、「要素の変数.getBoundingClientRect().top」で取得できます。
ちょっとややこしいですが、とりあえず書いてみてください。

scrollイベントの書き方はこのような感じです。

下記のコードを「console.log(vh);」の下に追記してください。

 // イベント付加
  window.addEventListener('scroll', function() {
      // 処理を書く
      // スクロール量
      const scroll = window.pageYOffset;
      console.log(scroll);

      // 要素の位置までの相対数値
      const targetDistance = secGreen.getBoundingClientRect().top;
      console.log(targetDistance);
  });

scrollイベントはwindowにaddEventListenerを付けていきます。

windowとはwindowオブジェクトのことで、オブジェクトの理解が必要になります。今はそんなに考え込まないようにして、windowオブジェクトは表示画面の情報の取得や操作ができるんだな。と頭に入れといてください。
そのうち解説講座を書きます。

index.js全体はこのような感じなります。

'use strict'; 

{
  // データ取得
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  console.log(pageTop);

  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  console.log(secGreen);

  // ビューポートの高さ
  const vh = window.innerHeight;
  console.log(vh);
  
  // イベント付加
   window.addEventListener('scroll', function() {
      // 処理を書く
      // スクロール量
      const scroll = window.pageYOffset;
      console.log(scroll);

      // 要素の位置までの相対数値
      const targetDistance = secGreen.getBoundingClientRect().top;
    console.log(targetDistance);
  });
}

このようになっていたらOKです。

これで、コンソール表示して、少しスクロールしてみましょう。

スクロールするたびに数字が表示されると思います。

1、3、 5となっているのがスクロール量ですね。

スクロール量は「window.pageYOffset」で取得できます。スクロール量もwindowオブジェクトから取得できるんですね。

1599、1597、1595となっているのが、要素の位置までの数値になります。

要素の位置までの数値は、「getBoundingClientRect().top;」で取得できます。ページの一番上から、secGreenの一番トップのラインまでの数値を出してくれます。(相対数値なので、スクロール位置により数値は変化する)

scrollイベントはスクロールをする度に、すごい数の実行を繰り返してくれます。

なので、スクロール量と、要素の位置までの数値はscrollイベントの中に入れて、スクロール度に実行してもらいます。(そうしないと、スクロール量が増えていかないので)

スクロールすると、スクロール量が増えて、要素の位置までの数値は減ります。スクロール量と、要素の位置までの数値を足すとどうなるでしょうか?

どこまでスクロールしても、スクロール量が増えて、要素の位置までの数値は減りますので、足し算すると常に一定の数値になります。

この式を利用して次のように書いていきます。

console.log(targetDistance);のすぐ下に追記してください。

  // 画面下から要素までの絶対数値
  const targetPosition = scroll + targetDistance - vh;

    // スクロール量がtargetPositionの位置を超えたら
    if ( targetPosition < scroll ) {
          pageTop.classList.add('fadeIn');
    } else {
          pageTop.classList.remove('fadeIn');
    }

この図の計算すると、scroll(スクロール量)とtargetPosition(scroll + targetDistance - vh)の数値はほぼ同じになります。

なので、if文を使い、targetPositionよりもscrollが大きくなると、fadeIn のクラスを付加するということができ、targetPositionよりも小さいとfadeInクラスを外すという分岐ができました。

このあたりは、イメージがなかなか沸かないと思いますが、何度か頭の中でシュミレーションしてみてください。

ちょっとややこしくなってきたので、図でイメージを作りました。

index.jsの全体のコードはこのような感じになりました。

'use strict'; 

{
  // データ取得
  // DOM取得
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  console.log(pageTop);

  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  console.log(secGreen);

  // ビューポートの高さ
  const vh = window.innerHeight;
  console.log(vh);

  // イベント付加
  window.addEventListener('scroll', function() {
  // スクロール量
      const scroll = window.pageYOffset;
      console.log(scroll);

      // 要素の位置までの数値
      const targetDistance = secGreen.getBoundingClientRect().top;
      console.log(targetDistance);

      // 要素までの絶対位置
      const targetPosition = scroll + targetDistance - vh;

      // スクロール量がtargetPositionの位置を超えたら
      if ( targetPosition < scroll ) {
          pageTop.classList.add('fadeIn');
      } else {
          pageTop.classList.remove('fadeIn');
      }
});
}

これで、トップへ戻るぼたんの設置は完了となります。

一度、表示を確認してみましょう。

このような感じになっていれば、完成です。

最後の仕上げをしていきましょう。

イベント付加と、処理を分けて書いてみましょう。

とりあえず、動きは確認済みなので、console.logの記述はすべて削除します。

そして、イベントの上に新たなfunction(関数)を作り、イベントの関数の中の処理をすべて新たに作ったfunctionに移します。

'use strict'; 

{
  // データ取得
  // DOM取得
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  // ビューポートの高さ
  const vh = window.innerHeight;
 
  // 処理
  // イベントで実行する処理
  function pageTopAction() {  /* functionを作りイベントの処理を移す */

      // スクロール量
      const scroll = window.pageYOffset;
      // 要素の位置までの数値
      const targetDistance = secGreen.getBoundingClientRect().top;
      // 要素までの絶対位置
      const targetPosition = scroll + targetDistance - vh;

      // スクロール量がtargetPositionの位置を超えたら
      if ( targetPosition < scroll ) {
          pageTop.classList.add('fadeIn');
      } else {
          pageTop.classList.remove('fadeIn');
      }
  }

  // イベント付加
  window.addEventListener('scroll', pageTopAction); /* 新たに作った関数名を入れる。()は付けない */
}

イベント付加のaddEventListenerのfunctionは削除して、新たに作った関数名を入れます。
その時の注意点ですが、addEventListenerに関数名を入れる場合は関数名の()は付けないようにします。

その理由は、スクロールするまでは実行しないので、()は付けないということみたいです。

少しコードが整理されて、みやすくなったのではないでしょうか?

実は、addEventListenerのscrollイベントは少し問題があります。

スクロールをする度にかなりの量のスクロール量を取得するのでscrollイベントを1ページで何個も使いすぎるとスクロールでカクついたり、ブラウザに負担がかなりかかってしまいます。

scrollイベントを2、3個ぐらいなら問題ないと思います。

なので、次はscrollイベントのブラウザへの負荷を減らす方法を書きたいと思います。

scrollイベントでブラウザの負荷を減らす方法

scrollイベントのブラウザの負荷を減らす方法の一つを紹介しますね。

scroll量の取得をsetTimeout()というJavaScriptがデフォルトで用意してくれている関数で、間引いていきます。

setTimeout()はタイマー機能をもっていて、指定した秒数まで処理を待ってくれます。

これをaddEventListenerの関数内で使っていきます。

index.jsを変更していきます。

まずは変数定義していきます。11行目の「const vh = window.innerHeight;」のすぐ下に追記してください。

 // リセット用変数
 let time = null;

  // タイマーの秒数
  const speed = 200;

変数timeはsetTimeout()をリセットするclearTimeout()という関数を使いリセットしていきます。

リセットとは、一度setTimeout()を完全に止めてしまうということです。

変数speedは、タイマーの時間です。単位はm秒なので、200で0.2秒になります。

次はsetTimeOut()と、clearTimeout()を使っていきます。

下記記述の中にscrollイベントの処理をすべて、入れてしまいます。

そして、スクロール量の取得がわかりやすいようにconsole.log()を追加しまししょう。

 // スクロールイベントを間引く
 clearTimeout(time);
 time = setTimeout(function() {

  // scrollイベントの処理を入れる

 }, speed);

処理は上から下に実行されますから、clearTimeout(time);で、setTimeout()キャンセルしてから、setTimeout()を実行します。scrollイベントを実行する度にキャンセルしてからsetTimeout()を実行する処理です。

それでは、index.jsの全体をみていきます。

'use strict'; 

{
  // データ取得
  // DOM取得
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  // ビューポートの高さ
  const vh = window.innerHeight;
  // リセット用変数
  let time = null;
  // タイマーの秒数
  const speed = 200;
  
  // 処理
  // イベントで実行する処理
  function pageTopAction() {
    // スクロールイベントを間引く
    clearTimeout(time);
    time = setTimeout(function() {

        // スクロール量
        const scroll = window.pageYOffset;
        console.log(scroll);  /* console.logを追加 */
        
        // 要素の位置までの数値
        const targetDistance = secGreen.getBoundingClientRect().top;
        console.log(targetDistance);  /* console.logを追加 */
        
        // 要素までの絶対位置
        const targetPosition = scroll + targetDistance - vh;
        
        // スクロール量がtargetPositionの位置を超えたら
        if ( targetPosition < scroll ) {
          pageTop.classList.add('fadeIn');
        } else {
          pageTop.classList.remove('fadeIn');
        }

    }, speed); /* setTimeoutを閉じる */
  }

  // イベント付加
  window.addEventListener('scroll', pageTopAction);
}

index.jsがこうなってたらOKです。

これで、ブラウザのコンソールを開いてスクロール量の取得を確認してみましょう。

スクロール取得が間引かれてるのが、わかりますでしょうか?

これで、ブラウザ負荷の対策はOKです。

speedの変数ですが、150m秒ぐらいでもいいかもしれないですね。調整してみてください。

これでトップへ戻るボタンの設置は完了なんですが、せっかくなんでさらにカスタマイズで、全ページでトップへ戻るボタンを機能するようにしてみましょう。

すべてのページでトップへ戻るボタン設置するカスタマイズ

,すべてのページにトップへ戻るボタンを設置するには、今のままではダメです。

すべてのページに今回基準にした「緑のセクション」があるわけではないですよね。なのですべてのページにあるのようなパーツを作って、そのパーツの絶対位置を取得して、トップへ戻るボタンを出現させたいと思います。

htmlでヘッダーのパーツを作っていきます

まずは、今回作るすべてのページにあるパーツ、「ヘッダー」を作っていきたいと思います。index.htmlでheaderを作っていきますね。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>PageTop</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
   <!-- ここに追記 -->
  <header class="header">
    <h1>LOGO</h1>
    <nav>
      <ul>
        <li>menu1</li>
        <li>menu2</li>
        <li>menu3</li>
        <li>menu4</li>
      </ul>
    </nav>
  </header>
  <!-- 追記終わり -->

  <div class="pagetop">
    <a href="#" class="pagetop__link">
      <span class="pagetop__text">TOP</span>
    </a>
  </div>


  <section class="sec-blue"></section>
  <section class="sec-yellow"></section>
  <section class="sec-green"></section>
  
  <script src="index.js"></script>
</body>
</html>

「ここに追記」の部分を追加しました。ロゴとナビメニューのパーツを作りましたが、今回は見た目を整えただけでここは関係はしません。

必要なのはheaderのパーツのみになります。

CSSでヘッダーを整えよう

次はCSSで見た目を整えていきましょう。

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
}

/* ここから追記 */
ul, li {
  padding: 0;
  margin: 0;
}

.header {
  max-width: 1100px;
  width: 100%;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background-color: #fff;
}

nav {
  max-width: 400px;
  width: 100%;
}

nav > ul {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

nav > ul > li {
  list-style: none;
}
/* 追記終わり */

.sec-blue {
  background-color: rgb(0, 140, 255, .5);
  height: 800px;
}

.sec-yellow {
  background-color: rgba(244, 244, 11);
  height: 800px;
}

.sec-green {
  background-color: rgb(129, 243, 129, .5);
  height: 800px;
}

/* トップへ戻るボタン */
.pagetop {
  width: 64px;
  height: 64px;
  position: fixed;
  bottom: 8%;
  right: 2%;
  transition: opacity .3s ease, visibility .3s ease;
  opacity: 0; /* デフォルトでは表示を消します */
  visibility: hidden; /* デフォルトでは表示を消します */
}

.pagetop__link {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background-color: #ffffff;
  border-radius: 50%;
  text-align: center;
  font-size: 18px;
  font-size: bold;
  line-height: 1.3;
  color: #333;
  text-decoration: none;
}

.pagetop__text {
  padding-top: 15px;
  position: relative;
}

.pagetop__text::before {
  content: "〈";
  display: inline-block;
  position: absolute;
  top: -13px;
  left: 50%;
  transform: translateX(-50%) rotate(90deg);
}

.pagetop__text::after {
  content: "〈";
  display: inline-block;
  position: absolute;
  top: -7px;
  left: 50%;
  transform: translateX(-50%) rotate(90deg);
}

/* TOPボタン出現後のスタイル */
.pagetop.fadeIn {
  opacity: 1; /* フェードインアニメーション */
  visibility: visible; /* ボタンを表示 */
}

/* ここから追記 */の部分を追記してください。これでCSSのスタイルが完了になります。

ブラウザに表示してみましょう。

このような表示になってれば、ヘッダーの設置は完成です。

次はJavaScriptでヘッダーの絶対位置を取得していきましょう。

JavaScriptでヘッダーの絶対位置を取得しよう

まずはheader自体を取得しまししょう。

index.jsに追記していきます。

/* ここに追記 */の箇所を追記してください。

{
  // DOM取得
  // headerを取得
  const header = document.querySelector('header'); /* ここに追記 */
  console.log(header); /* ここに追記 */
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  // ビューポートの高さ
  const vh = window.innerHeight;
  // リセット用変数
  let time = null;
  // タイマーの秒数
  const speed = 200;
  
  // 処理
  // イベントで実行する処理
  function pageTopAction() {
    // スクロールイベントを間引く
    clearTimeout(time);
    time = setTimeout(function() {

        // スクロール量
        const scroll = window.pageYOffset;
        console.log(scroll);
        
        // 要素の位置までの相対数値
        const targetDistance = secGreen.getBoundingClientRect().top;
        console.log(targetDistance);
        
        // 要素までの絶対位置
        const targetPosition = scroll + targetDistance - vh;
        
        // スクロール量がtargetPositionの位置を超えたら
        if ( targetPosition < scroll ) {
          pageTop.classList.add('fadeIn');
        } else {
          pageTop.classList.remove('fadeIn');
        }

    }, speed); /* setTimeoutを閉じる */
  }

  // イベント付加
  window.addEventListener('scroll', pageTopAction);
}

これでheaderが取得できてると思います。
デベロッパーツールを開いて、コンソールをみてみましょう。

headerのDOM要素が取得できましたね。これでもう下準備は完了です。

ここからは先程の復習みたいになりますが進めていきましょう。

次はgapという変数を用意してそこに数字の300を変数に入れておいてください。

'use strict'; 

{
  // DOM取得
  // headerを取得
  const header = document.querySelector('header');
  console.log(header);
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  // ビューポートの高さ
  const vh = window.innerHeight;
  // リセット用変数
  let time = null;
  // タイマーの秒数
  const speed = 200;
  // 調整用変数
  const gap = 300; /* ここにgapを追記 */
  
  // 処理
  // イベントで実行する処理
  function pageTopAction() {
    // スクロールイベントを間引く
    clearTimeout(time);
    time = setTimeout(function() {

        // スクロール量
        const scroll = window.pageYOffset;
        console.log(scroll);
        
        // 要素の位置までの相対数値
        const targetDistance = secGreen.getBoundingClientRect().top;
        console.log(targetDistance);
        
        // 要素までの絶対位置
        const targetPosition = scroll + targetDistance - vh;
        
        // スクロール量がtargetPositionの位置を超えたら
        if ( targetPosition < scroll ) {
          pageTop.classList.add('fadeIn');
        } else {
          pageTop.classList.remove('fadeIn');
        }

    }, speed); /* setTimeoutを閉じる */
  }

  // イベント付加
  window.addEventListener('scroll', pageTopAction);
}

gapはヘッダーから計算してどの位置でトップへ戻るボタンを表示するかを調整する為の変数です。

gapでトップへ戻るボタンの出現位置を調整するイメージになります。

次は何をするかというと、ヘッダーの絶対位置の取得まで進めていきます。これは先程やりましたので復習になります。

scrollイベントは絶対位置の習得がキーになりますので、イメージを固めていってください。

まずはgetBoundingClientRect()でheaderの相対位置を取得します。

headerの相対位置なので、secGreenだったところをheaderに変更して、今回はheaderの一番下の位置を取得したいので「getBoundingClientRect().bottom」と書くことでheader要素の一番下の相対位置を取得できます。

なので、もともと「.top」となっていたところを「.bottom」に変更します。

これでheaderの一番下の相対位置が取得できました。みやすいようconsole.log(targetDistance)のみにしておいてください。

ブラウザでコンソールを出してスクロールしてみてください。headerがみえなくなるまでスクロールすると、値がマイナスになって変化していると思います。

値が変化するので相対位置になります。ちなみにこの値は画面表示(ビューポート)の一番上からの値になります。なので見えなくなるとマイナスの数値になります。

次はこの相対位置の値をもとに絶対位置をもとめます。

「- vh」となっているところを「+ gap」に変更します。そして下console.log(targetPosition);を追加してください。

どういうことかというと、前回はvh(ビューポートの高さ)を絶対値から引くことで、ビューポートの一番下でsecGreenがみえるとボタンが現れる設定だったんですが、今回はビューポートの一番上が基準にするので、引き算ではなく足し算でビューポートの上がボタンが現れる基準となります。

この辺りは何回か書いてみて体で覚えてください。

一度、この状態ブラウザのコンソールでスクロールしてください。

スクロールすると、当たり前ですが相対値が変化して、絶対値がスクロールしても変化しませんね。これで「header + gap」の絶対値を取得できました。

変数gapの値を変更するとボタンが表示される位置を変更することが出来ます。今回は300にしていてheaderの下から300スクロールするとボタンが表示されるということですね。

index.jsはこのようになりました。

'use strict'; 

{
  // DOM取得
  // headerを取得
  const header = document.querySelector('header');
  // pagetopボタン
  const pageTop = document.querySelector('.pagetop');
  // トップへ戻るボタンを表示する位置のsection
  const secGreen = document.querySelector('.sec-green');
  // ビューポートの高さ
  const vh = window.innerHeight;
  // リセット用変数
  let time = null;
  // タイマーの秒数
  const speed = 200;
  // 調整用変数
  const gap = 300; /* ここにgapを追記 */
  
  // 処理
  // イベントで実行する処理
  function pageTopAction() {
    // スクロールイベントを間引く
    clearTimeout(time);
    time = setTimeout(function() {

        // スクロール量
        const scroll = window.pageYOffset;
        
        // headerの一番下の相対数値
        const targetDistance = header.getBoundingClientRect().bottom; /* secGreenをheaderに変更、topをbottomに変更*/
        console.log(targetDistance);
        
        // 要素までの絶対位置
        const targetPosition = scroll + targetDistance + gap;
        
        // スクロール量がtargetPositionの位置を超えたら
        if ( targetPosition < scroll ) {
          pageTop.classList.add('fadeIn');
        } else {
          pageTop.classList.remove('fadeIn');
        }

    }, speed); /* setTimeoutを閉じる */
  }

  // イベント付加
  window.addEventListener('scroll', pageTopAction);
}

それではブラウザでコンソールを出して本当にスクロール量が300でトップへ戻るボタンが表示されるのかみてみましょう。

スクロール量を間引いているので、わかりずらいですが300ぐらいでボタンが表示されているのがわかると思います。

ヘッダーを基準にすることで全ページにこのボタンを設置して機能するように設定することができました。

ページトップへ戻るボタンの設置は、これで完了になります。

今回の内容でわからない部分があれば、完成のデータをこちらからダウンロードできます。

scrollイベントは1pxごとにJavaScriptを実行してしまうのでたくさん設置すのには向いてないのですが、スクロールアニメーションをたくさん設置するなら、こちらの記事の方法を試してみてください!!

scrollイベントは何も無いところにも設定できるというメリットがあるのでこちらもケースバイケースで使えるので、使い分けてみてください。

最後までお読みいただきまして、ありがとうございました!!

もし実装などで上手くいかないことありましたら、下記ボタンからご相談いただけましたらありがたいです。

この記事を書いた人

アバター

トノムラマサシ

masashi
Webサイト屋兼ブロガー|過去に企業常駐などを経て現在はフリーランスでディレクションとコーダーとして活動しております。JS大好きなのでJSの仕事依頼お待ちしております。京都在中で5人家族。