Bootstrap4 + jQuery で、navbarをheader画像の下から自然にStickyスクロール

どうも、プログラミング独学初心者の てくぽこ です。

記事を書きながらいろいろ調べていると、
「あっ…これこんなやり方あったんじゃん…」
って激萎えすること、ありますよね。
「こんな記事書きます！」宣言も簡単じゃないなぁ…って思います。
まぁ下調べが甘かった自分が悪い。

とはいえ宣言はしたものだったので、書きます。いざ。

#【要求】長いスクロール画面でも、ナビゲーションバーにはいつでもアクセスしたい。
#【要求】なおかつ、ページの最上部ではheader画像の下にナビゲーションバーを配置したい。
要は、`navbar`classを持つdivタグに直接`style="position:fixed;"`を書き込むだけじゃ足りない、という状況です。
こんな要求ってあるんでしょうか？
実務経験ゼロの身にはよくわかりません…

が、自分の要求としてはあったので実装してみました。
僕なんかが思いつくので多分当然のようにあるものかと。

私の言葉足らずでイメージが湧きにくいと思いますので、
結果から知りたい方は[こちら](#結果)から。

##実行環境
- Safari バージョン12.1.2 (14607.3.9)
- jQuery 3.4.1
- Bootstrap 4.3.1 Bundle

##方針
- `navbar`クラスに`style="position:fixed;"`を、jQueryで画面スクロール操作に合わせて動的に付与したり外したりします。→`$('#ナビゲーションバーのID').addClass('position:fixed属性を与えるクラス名')`
- header画像のサイズ分だけ下にスクロールしたら(`$(window).scrollTop() > headerMarginTop`)、mainタグにnavbarの高さ分だけpaddingを加えます。
- header画像の縦横比をもとに、閾値となる高さは%指定します。→`background: url("test.jpg"); padding-top: (test.jpgの高さ/横幅)%;`
- ウィンドウのリサイズにも対応したいので、閾値となる高さはjQueryでイベント取得します。

##コード
- html : test.html
- CSS : test.css
- JavaScript : test.js

概要抜粋にて掲載。

```html:test.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">

    <link rel="stylesheet" href="bootstrap-4.3.1-dist/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="test.css">

    <title>テストページ</title>

　（中略）

  </head>

  <body>

  <div class="header">
    <!-- ここにCSSで指定した画像データが挿入される -->
  </div>

  <!-- ポイント！ -->
  <!-- navbar に ID "Float-nav" を付与 -->
  <!-- margin を 0 に設定 -->
  <nav class="navbar navbar-expand-sm navbar-dark bg-dark m-0 pt-3 pb-3" id="Float-nav">

    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Navbar" aria-controls="Nabvar" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>

    <a class="navbar-brand" href="#">Navbarのstickyスクロールテスト</a>

    <div class="collapse navbar-collapse justify-content-end" id="Navbar">

      <ul class="nav nav-pills">
        <li class="nav-item" >
          <a class="nav-link" href="#">見出し１</a>
        </li>
        <li class="nav-item" >
          <a class="nav-link" href="#">見出し２</a>
        </li>
        <li class="nav-item" >
          <a class="nav-link" href="#">見出し３</a>
        </li>
      </ul>

    </div>
  </nav>

  <div class="main clearfix d-flex flex-row justify-content-center align-items-stretch">

    <div class="side-content d-none d-md-block">
      <p>これはサイドバーです。</p>
    </div>

    <div class="maincontent">
      <p>１，ここはメインコンテンツです。</p>

      （中略）

      <p>１０，ここはメインコンテンツです。</p>
    </div>

  </div>

  <div class="footer">
    <p>これはフッターです。</p>
  </div>

  <script type="text/javascript" src="test.js"></script>

  </body>
</html>
```  

HTMLファイル内でnavbarのmarginは0に設定してあります。
レスポンシブ対応にしています。


```css:test.css

/* 略 */

.navbar-fixed {
    top: 0;
    z-index: 100;
    position: fixed;
    width: 100%;
} /* navbarに付け外しするクラスの設定。position: fixed; と z-index:100;がポイント */

.header {
  width: 100vw;
  max-width: 100%;
  height: 0;
  text-align: center;
  background: url("test.jpg");
/* ポイント！ */
/* test.jpgの画像サイズ H:1200 x W:605 , H/W = 0.504166666666667 */
/* headerの画像サイズ横幅いっぱいウィンドウに表示されるようにpadding-topを設定します*/
  padding-top: 50.4166666666667%;
  background-position: left top;
  background-repeat: no-repeat;
  background-size: 100vw;
}

/* 略 */

```
header画像はすべて`padding-top`領域内に表示されるようcssで指定している形ですね。

```js:test.js
// ナビゲーションバーのstickyスクロール機能実装
var headerPaddingTop = 0;
$(document).ready(function() {
  // ポイント！
  // 画面load時とresize時に動的に表示領域幅を取得し、
  // header領域のMargin-topの高さを画像サイズにあわせ変更
  $(window).on('load resize',function () {
      headerPaddingTop = window.innerWidth * 0.504166666666667;
  });

  // ポイント!
  // 画面scroll時に、表示位置の上端とheaderのpaddingとの比較によって、
  // Float-navに対しnavbar-fixedクラスの与奪を判定
  // と同時に、Float-navの高さを取得して、あとに続く<div>親要素にpaddingを与奪
  $(window).scroll(function () {
    if ($(window).scrollTop() > headerPaddingTop ) {
      $('#Float-nav').addClass('navbar-fixed');
      $('.main').css('padding-top', $('#Float-nav').outerHeight(true));
    }
    if ($(window).scrollTop() <= headerPaddingTop ) {
      $('#Float-nav').removeClass('navbar-fixed');
      $('.main').css('padding-top', 0);
    }
  });
});

```
繰り返しますが、画面幅の取得やスクロール量に応じたクラスの与奪はjQueryによって制御します。`$().addClass('クラス名')`
また、**navbarの後に続く要素に、navbarの高さだけpaddingを追加したり消したりしている**部分が重要です。

navbarにposition:fixedを

- 追加したとき→後の要素にpadding追加
- 削除したとき→後の要素のpaddingを削除

これがないと、閾値点でいきなり後続要素がnavbarにめり込みます。


###結果
わかりにくいですが、黒い帯がナビゲーションバー`navbar`です。
header画像の表示領域以外は表示上端にくっついてきます。
逆に下からスクロールしてheader画像まで戻ってくると、
header画像の下にnavberが張り付きます。

それでありながら、navbarと他の要素との位置関係の挙動に
不連続点がないようにできました。

![Stickyスクロール.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402376/275bd08c-5b51-b9a3-9921-3151db157f8c.gif)

また、ウィンドウのリサイズにもしっかり対応しています。
どんな表示サイズにも、必要な高さでclass与奪制御がしっかりできています。

![レスポンシブ対応.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402376/532f26d2-5f94-3311-b418-241d02c4553a.gif)

## まとめ
いちおうChrome(バージョン: 76.0.3809.132（Official Build） （64 ビット）)と
Safariで確認しましたが大丈夫でした。

ページの下の深い方でnavbarにあったリンクを操作したいとき、トップに戻るjavascriptのボタンで画面遷移するヌルッとした動作が、いつも冗長に感じてしまうんですよね…。おしゃれだけど…。シンプル性…。

また実は今回、上記内容と合わせて
初めてBootstrapのレスポンシブに挑戦してみたのですが、
できてみるとやっぱり楽しい！


まずは、少なくとも自分の実装してみたい機能だけは最低限実装できるよう、これからも少しずつ勉強を深めていければと思います。

##参考記事
[【JS】スクロールしたら画面途中にあるグローバルナビを画面上部に固定する方法](https://nodoame.net/archives/8610)
- こちらを参考に、navbar自体の高さを考慮に加えました。