JS スクロールするとヘッダーのナビが画面上部に固定されついてくる方法

今回はヘッダー内のナビゲーションが、スクロールすることで画面上部に接触した際に、
画面上部に固定される方法をjQueryを使用せずにやっていきます。
（完成系コードは最下部）

_HTMLコード_
```
<body>
  <header>
    <h1 id="headerH1">タゴ'sクリエイト</h1>
    <nav>
      <p id="headerFixed">Global-Navigation</p>
    </nav>
  </header>
  <main>
    <div>
      <h2>Content</h2>
    </div>
    <div>
      <h2>Content</h2>
    </div>
　　　　　　　　　　　　　・
　　　　　　　　　　　　　・
　　　　　　　　　　　　　・
　　　　　　　　　　　　　・
  </main>
</body>
```
![スクリーンショット 2022-04-01 18.01.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/5c00713c-06bc-2bce-6a3e-ba9f31b13178.png)
とりあえずHTMLとCSSを使用して写真のようなものをブラウザに表示しました。
この「Global-Navigation」に対して、
下へスクロールすると画面上部にくっついて固定されるものをJSで記述していきます。

_JSコード_
```
window.addEventListener('load',fixedCheck); //１
window.addEventListener('resize',fixedCheck); //２
window.addEventListener('scroll',fixedCheck,{passive:true}); //３
function fixedCheck(){ //４
  let globalN=document.getElementById('headerFixed'); //５
  let fixedJudge=globalN.getBoundingClientRect().top; //６
  if(fixedJudge<=0){  //７
    globalN.classList.add('fixed'); //８
  }
}
```
_CSSコード_
```
.fixed{
  position: fixed;
  width: 100%;
  top: 0;
  left: 0;
}
```
![スクリーンショット 2022-04-01 18.08.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/cf9d0dad-3bf4-01bb-0909-3d63637f5af4.png)
この記述で画像の様な感じになります。
それでは「//番号」の順で1行ずつ説明していきます。

１、まず「addEventListener」で、ウィンドウをロードした時に「fixedCheck」関数を呼び出します。（この「fixedCheck」は後で説明しますが、大まかに言うと「HTMLの要素にclassを追加するか判定」という意味です）
２、次にまた「addEventListener」を使い、今度はウィンドウの画面サイズを変更した時に「fixedCheck」関数を呼び出します。
３、またまた「addEventListener」を使い、ウィンドウをスクロールした時にも「fixedCheck」関数を呼び出します。

ここで第三引数に「{passive:true}」を入れていることの説明を簡単にします。
「スクロールジャンク」と呼ばれる、主にモバイル端末で起きる、スクロールの際JSの処理待ちのためのカクカクする現象があり、
これは「PreventDefault」という処理を中断して他の処理をさせるメソッドがあるかないかをスクロールのたびにブラウザ側がチェックしているために起こっています。
「addEventListener」の第三引数に「{passive:true}」を入れることで、事前に
このページには「PreventDefault」メソッドがないよ！
と宣言しているということになります。
なので、「{passive:true}」を入れることによって、「スクロールジャンク」を防ぎスムーズにスクロールできるようになるというわけです。

４、次は先ほどの「fixedCheck」関数を記述していきます。
５、まずはHTMLのグローバルナビ（今回は「headerFixed」）を「getElementById」で取得し、変数「globalN」変数に代入します。
６、次に「getBoundingClientRect().top」で「globalN」要素の上辺を基準とする、ブラウザ上の高さ位置を取得し、「fixedJudge」変数へ代入します。

ここで「getBoundingClientRect」について説明です。
これは「要素.getBoundingClientRect().top」とすることで、
(今回Xは「top」を指定してますが、他の指定種類については後述します)
要素のボックス（border、padding含む）の上辺の座標が、ブラウザの表示部分（ビューポート）の基準点である左上（左上が「0,0」となっている）を基準として、どの位置にいてるかを数値として取得できます。
この「top」の他にも「bottom,left,right」等があり、それぞれボックスの下辺、左辺、右辺を意味します。
例えば「スクロールしてh1要素が完全に画面から上になくなるとイベントを起こしたい」等の時は、
「bottom」を使用するといいと言うことですね。

７、「fixedJudge（「globalN」上辺のブラウザ上での高さ位置）」が0以下（画面上部よりさらに上に行き、見えない状態）の場合という条件式を記述。
８、「DOM要素.classList.add('クラス名');」とすることで、取得したDOM要素に、CSSで使用できるクラス名（HTMLでの「class=""」と同じ）を追加できます。今回の記述だと、ID名「headerFixed」の要素に「fixed」というクラスを付与する、と言う意味になります。

記述コードに関しての説明は以上になりますが、このコードだと一度付与されたクラスはずっと付与された状態のままで、
![スクリーンショット 2022-04-02 16.48.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/b9e4a2e6-b84c-cc0b-2f45-898c436ab675.png)
画像の様な状態になってしまいます。
そこで「fixedJudge」が０より上（画面上部より下に行き、見える状態）になると付与したクラスを削除したいと思います。
クラス付与が「classList.add」なのに対し、クラス削除は「classList.remove」で行えます。

ここでよく間違いがちなのが付与を追加したとき同様に「グローバルナビ」要素に「remove」メソッドを適用してしまうことです。
「グローバルナビ」が上部固定された状態だと、どれだけスクロールしたとしても座標が「０」から変わることはあり得ません。なので、

_JSコード_
```
window.addEventListener('load',fixedCheck);
window.addEventListener('resize',fixedCheck);
window.addEventListener('scroll',fixedCheck,{passive:true});
function fixedCheck(){
  let h1Bottom=document.getElementById('headerH1'); //変更点１
  let globalN=document.getElementById('headerFixed');
  let fixedJudge=h1Bottom.getBoundingClientRect().bottom; //変更点２
  if(fixedJudge<=0){
    globalN.classList.add('fixed');
  }else{ //変更点３
    globalN.classList.remove('fixed');  //変更点４
  }
}
```
変更点１、HTMLのヘッダー内にある「h１」を取得し変数「h1Bottom」に代入し、
変更点２、次に「getBoundingClientRect().bottom」で「h1Bottom」要素の下辺を基準とする、ブラウザ上の高さ位置を取得し、「fixedJudge」変数へ代入。（この時点で「if文」条件式の基準となる座標が「グローバルナビの上辺」から「h１要素の下辺」に変わっています。）
変更点３、if文条件式以外の場合の「else」を追加。
変更点４、「globalN」から「fixed」クラスを削除する文を記述。

この様に記述することで、
h１要素の下辺がビューポート上辺より上（h１要素が見えない状態）にある時は、「globalN」に「fixed」クラスを付与し、
ビューポート上辺より下（h１要素が見える状態）の時は「globalN」から「fixed」クラスを削除する、
と言う記述の意味になりました。

![スクリーンショット 2022-04-01 18.01.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/73dbca49-ecc8-2972-4b6b-910c50d5af5d.png)
↓↓↓↓↓↓↓↓↓↓↓
![スクリーンショット 2022-04-01 18.08.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/a3c405b9-5042-41a5-0aaf-7325640affa1.png)
↓↓↓↓↓↓↓↓↓↓↓
![スクリーンショット 2022-04-02 17.09.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/a92af0c9-8413-52c7-1121-ccb092aa1b45.png)
実際にブラウザに表示してみるとこのようになりましたね。

これでナビが画面上部に固定されついてくる方法の説明は以上になります。
ご購読ありがとうございました！


_追記_

コメントで「position: sticky;」だと同じことができると言っていただき、検証してみました！
先ほどまでのJSコードを全て消し、CSSコードの「nav」要素に、

_CSSコード_
```
nav{
  height: 100px;
  margin-bottom: 40px;
  position: sticky; /* sticky指定 */
  top: 0px; /* stickyとして固定したい位置 */
}
```
このように追加してみます。なお、この「top」というのは画面上部に要素を固定するという意味で、
これを「bottom」にすると画面下部に固定するという意味になります。
![スクリーンショット 2022-04-04 7.37.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/36206d94-21fc-181e-cc09-436e74a3bae6.png)
上記コードを記述したところうまく適用できませんでした。
これは、「nav」要素に対して「position: sticky;」を記述すると、
今回だと「nav」要素の親要素である「header」要素内でのみ「sticky」が適用し追従するからです。
例として「h１」要素に対して「sticky」を適用してみると、
![スクリーンショット 2022-04-04 7.42.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/18c2d1da-8c7b-82a7-9961-6721ec5c524f.png)
最初は画面上部に固定されていますが、
![スクリーンショット 2022-04-04 7.43.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/77f02063-cd1d-a6db-b0cd-d63fe8e75d5c.png)
「header」の最下部（「nav」要素の下部の位置）まで「h１」要素が達すると、
それ以上したに行くことができず、追従が終わっているのがわかります。
この様に「sticky」を設定すると、設定した要素に対する親要素の範囲内でのみ、上部に固定する様になります。

この「sticky」を使用して最初に記述したHTMLに対してナビを上部固定する時は、

_HTMLコード_
```
<body>
  <header>
    <h1 id="headerH1">タゴ'sクリエイト</h1>
  </header>
  <nav>
    <p id="headerFixed">Global-Navigation</p>
  </nav>
  <main>
　　　　　　　　　　　　　・
　　　　　　　　　　　　　・
　　　　　　　　　　　　　・
　　　　　　　　　　　　　・
  </main>
</body>
```

_CSS_
```
nav{
  height: 100px;
  margin-bottom: 40px;
  position: sticky; /* sticky指定 */
  top: 0px; /* stickyとして固定したい位置 */
}
```
このコードの様に「nav」要素をheaderから抜き出して、
「nav」要素の親要素が「body」要素という状態を作ると
![スクリーンショット 2022-04-05 0.28.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2521240/406713e1-08be-4c92-9582-c0b8334b58f3.png)
この様に画面上部にしっかりと固定されました！