SevenSoundSpace

スクロールと連動して消えたり現れたりするTopMenu

考え方

  1. HTML:シャッ と隠したい要素をまるごと一つのタグで括る
  2. CSS:position:fixed で固定配置
  3. JavaScript(jQuery):現在のスクロール位置を取得し、top の値を調整する

実際の記述

See the Pen hideTopMenu by SevenSound (@SevenSound) on CodePen.

  1. HTML

    一例として、<header> 要素をまるごと隠す対象とします。

    ナビは各sectionへのページ内リンク。

    <body>
      <header>
        <nav>
          <ul>
            <li><a href="#section1">section1</a></li>
            <li><a href="#section2">section2</a></li>
            <li><a href="#section3">section3</a></li>
          </ul>
        </nav>
      </header>
    
      <main>
        <section id="section1">
          ・・・
        </section>
        <section id="section2">
          ・・・
        </section>
        <section id="section3">
          ・・・
        </section>
      </main>
    </body>
  2. CSS

    まずはザッとコードを紹介しちゃいます。

    header{
      position: fixed;
      top: 0px;
      left: 0px;
      width: 100%;
      z-index: 10;
      background: rgba(0,0,0,0.7);
      transition: .3s;
    }
    nav{
      overflow: hidden;
    }
    nav li{
      float: left;
      margin: 10px;
    }
    nav li a{
      color: #fff;
      text-decoration: none;
    }

    それでは小分けに解説していきますね。

    • 隠したい要素<header>をfixedで固定配置した上で
      header{
        position: fixed;
        top: 0px;
        left: 0px;
    • 横幅めいっぱいにメニュー領域を広げて
        width: 100%;
    • 他の要素の裏に隠れないようにz-indexで要素を前面に配置。
        z-index: 10;
    • 背景色には透過の黒をチョイス
        background: rgba(0,0,0,0.7);
    • 0.3秒で消えたり出たりするアニメーションをつけてみる。
        transition: .3s;
      }
    • ナビはfloatで横並びに表示。
      nav{
        overflow: hidden;
      }
      nav li{
        float: left;
        margin: 10px;
      }
    • リンクの文字は白の下線なし。
      nav li a{
        color: #fff;
        text-decoration: none;
      }

    これでひとまず、ページTopに固定でかぶさるナビが表示されました。

  3. JavaScript(jQuery)

    最後に、jQueryで隠せるようにしましょう。

    こちらもまずは全コードを先に紹介しますね。

    $(function(){
      var hideElement = "header"; // 隠す対象の要素名
      var hideHeight = $(hideElement).height(); // TopMenuの総高
      var startPos = hideHeight; // 隠し始めるスクロール位置
      var currentPos = 0; // 現在位置を保持するための変数
    
      // スクロール時
      $(window).scroll(function(){
        if($(hideElement).css("position") == "fixed"){
    
          // 下向きスクロール
          if(currentPos < $(this).scrollTop()){
            currentPos = $(this).scrollTop(); // まずは現在位置を更新
            // 現在位置が、startPosを超えていたら隠す
            if(startPos < currentPos){
              if($(window).scrollTop() > 0){
                $(hideElement).css("top", "-" + hideHeight + "px");
              }
            }
    
          // スクロール値が前回と同じなら何もしない ※IE対策
          }else if(currentPos == $(this).scrollTop()){
    
          // 上向きスクロールでは、必ずメニューを表示
          }else{
            currentPos = $(this).scrollTop();
            $(hideElement).css("top", 0 + "px");
          }
        }
      });
    });

    (行コメント文と重複する部分もありますが...)順番に解説していきます。

    • ※前提としてjQueryを使用するので、事前にjQueryライブラリ読み込みを忘れずに。
    • まずは各種変数定義。 要素名が変わっても簡単に転用できるように変数化。
      $(function(){
        var hideElement = "header"; // 隠す対象の要素名
    • 隠したい要素自身の高さをCSSの値から取得し、保持しておく。
        var hideHeight = $(hideElement).height(); // TopMenuの総高
    • スクロール位置のどの辺りから隠すかを調整する変数を定義しておきました。
        var startPos = hideHeight; // 隠し始めるスクロール位置
    • スクロールする毎に前回位置と比較するための変数。
        var currentPos = 0; // 現在位置を保持するための変数
    • それではここから実処理です。 スクロールが実行されたとき、かつ、指定した要素がfixedのときに処理します。
        // スクロール時
        $(window).scroll(function(){
          if($(hideElement).css("position") == "fixed"){
    • まずは下向きにスクロールした(すなわちスクロール後の位置が直前の値より大きくなる)ときの処理。
      CSSを書き換えて、topの位置をずらすことでウィンドウの外へ移動させます。
            // 下向きスクロール
            if(currentPos < $(this).scrollTop()){
              currentPos = $(this).scrollTop(); // まずは現在位置を更新
              // 現在位置が、startPosを超えていたら隠す
              if(startPos < currentPos){
                if($(window).scrollTop() > 0){
                  $(hideElement).css("top", "-" + hideHeight + "px");
                }
              }
    • 今度はスクロール位置が直前と等しいときの処理です。
      「スクロールする = スクロール位置が変わる」と考えられるため、一見この分岐は通らないように思えます。
      が、ブラウザによってスクロールが終わった後に "同じスクロール位置で"スクロールイベントが呼ばれることがあります。
      そこで、この分岐では「スクロール位置が直前と等しければ "何もしない"」ようにしています。
            // スクロール値が前回と同じなら何もしない ※IE対策
            }else if(currentPos == $(this).scrollTop()){
    • そして最後の分岐、上向きスクロール時の処理です。
      今回は上向きにスクロールした際に必ずメニューを再表示するようにしました。
      再表示の仕組みは隠すときの逆、つまりtopの位置を元の0pxに戻しています。
            // 上向きスクロールでは、必ずメニューを表示
            }else{
              currentPos = $(this).scrollTop();
              $(hideElement).css("top", 0 + "px");
            }
          }
        });
      });

    これが今回のTopMenuを消したり出したりするjQueryの使い方です。

    今回はウィンドウ上部にくっついているメニューのため、上スクロールで戻す先は0pxでした。

    が、この戻す位置や動かす方向を変えることで色々な動きを実現できるようになります!

以上、少し長くなりましたがTopMenuをスクロールに合わせて動かす方法について分かったことのまとめです。

最後の方に書いた通り、これをベースに色々と応用できると思うので、サイトデザインに合わせた動きを考えてみるのも面白いのではないでしょうか。

ちょっと応用

jQueryのコードをいじってて思いついた小ネタをご紹介

  1. 隠す要素にpadding/marginが記述されている場合

    上述のコードだとTopMenuのheight分のみtopを変更するため、もしpaddingやmarginが設定されていたらちょびっと隠しきれない半端な動作になってしまう。

    そこで、padding/marginの値も取得して、隠す高さに足し込んでおくことで回避する。

    • 上記jQueryのコードを一部抜粋し、変更内容をbefore → afterにて。
      まずは【before】
        var hideHeight = $(hideElement).height(); // TopMenuの総高
    • それを変更した【after】
        var hideMarginTop = parseInt($(hideElement).css('margin-top'), 10); // margin-top取得
        var hidePaddingTop = parseInt($(hideElement).css('padding-top'), 10); // padding-top取得
        var hidePaddingBottom = parseInt($(hideElement).css('padding-bottom'), 10); // padding-bottom取得
        var hideHeight = $(hideElement).height() + hideMarginTop + hidePaddingTop + hidePaddingBottom; // TopMenuの総高

      ここでparseIntという関数を使用しているが、これは1番目の引数に指定された文字列を整数に変換する関数。(文字列は先頭の文字から順に調べていき、数値でない文字が現れる直前までの文字列を整数に変換する)

      2番目の引数は基数。 指定することで、2/8/10/16進数のどの解釈で整数化するか選択することができる。

  2. 隠す要素の次にくる要素の開始位置をぴったり調整する

    TopMenuをfixedにするということは、そのままだと次にくる要素(今回はmain)がTopMenuの裏側に潜り込んでしまう。

    そうなっても何とかなるようにTopMenuの背景色を透過させたが、やっぱり基本的は重ならないのが望ましいのではないか。

    かといってCSSに固定値のpaddingを書き込んでしまうと、今度はTopMenuの内容を変えたときにまた調整しなくてはいけない...。

    そこで! せっかく取得・計算した「TopMenuの総高」があるのだから、それを使ってぴったり隙間を空けようじゃないか、というのが狙い。

    • hideHeightを計算した後であればどこでも良いので、下記の処理を追加すればOK!
       $("main, section").css("padding-top", hideHeight + "px"); // TopMenuの次要素の開始位置を調整する

      mainだけでなくsectionにも同じだけpadding-topを設定したのは、ページ内スクロールで各セクションへ飛んだ際にもTopMenuがかぶらないように配慮している。

  3. ウィンドウサイズを変えて高さが変わる場合

    本サイトもそうだが、ウィンドウサイズに応じて可変する要素は往々にして存在する。

    そこで、ウィンドウサイズ変更のイベントが発生したら隠す要素の総高を再計算する仕組みを追加する。

    • 以下のコードを追加する。
        // レスポンシブデザインなどwindowリサイズ時にヘッダー高が変わる場合のケア
        // リサイズ操作が終わったときだけ処理する
        var timer = false;
        $(window).resize(function(){
          if(timer !== false){
            clearTimeout(timer);
          }
          timer = setTimeout(function(){
            // 実処理
            hideMarginTop = parseInt($(hideElement).css('margin-top'), 10); // margin-top再取得
            hidePaddingTop = parseInt($(hideElement).css('padding-top'), 10); // padding-top再取得
            hidePaddingBottom = parseInt($(hideElement).css('padding-bottom'), 10); // padding-bottom再取得
            hideHeight = $(hideElement).height() + hideMarginTop + hidePaddingTop + hidePaddingBottom; // 総高を再計算
            $("main, section").css("padding-top", hideHeight + "px");
          }, 500);  // 処理タイミングはリサイズ終了の500ms後
        });

      ウィンドウサイズ変更中、常に実処理を繰り返すのは無駄な負荷を生じやすいケースも考えられるため、今回はタイマーを使って「最後にウィンドウサイズを変更してから500ミリ秒の間にウィンドウサイズ変更がこなければ実処理が発動する」仕組みとした。

このように、ページの体裁によってケアする処理は多種多様に存在する。

(最適解かどうか推し量り難いが)それぞれの打開策を考える過程は難しいほどに面白い。