月刊JAVASCRIPT – コーダーは「JS」がやめられない! –

\うまぴょい/

どうも、先行ウマ娘を無限に育成しているDJよろずやです。

最近はAPEXではなく、やっとこさリリースされたウマ娘をひたすらプレイしています。
愛馬はマチカネフクキタルミホノブルボンキングヘイローです。
みなさんもうまぴょい(URA優勝)を目指してウマ娘をプレイしてみません?

ウマ娘 プリティーダービー 公式ポータルサイト|Cygames
ウマ娘 プリティーダービー公式ポータルサイトです。メディアミックスコンテンツ『ウマ娘 プロジェクト』に関する情報をお届けします。

モンスターハンターライズも絶賛プレイ中ですが、HR60~↑とかになってるのでひたすら集会場クエスト埋めるか、護石ガチャするくらいになっています。

閑話休題

今まではflexシリーズの記事を書いていましたが、今回からJavascript(jQuery)シリーズを始めてみようと思います。

最近はあまりコーディングをする機会はありませんが、自分が今まで書いてきたjQueryの備忘録がてら記事にしてみようかと思います。
どこかの誰かの参考になればいいかなと。

はい、拍手。👏

月刊JAVASCRIPT

今回はコーディングするときに使わない時はないくらい毎回書いているjQueryを紹介します。
※jQueryプラグイン(スライダーなど)ではなくjQueryで記述したコード

  • Headerメニューの開閉
  • メニューリンク押下時にセクションへの自動スクロール&メニューを閉じる
  • Pagetopボタン
  • 画像のPC/SP切り替え

を順番に説明していきます。
懇切丁寧に説明していくので、jQueryの処理1つで1記事のペースで行こうと思います。


というわけで、一旦最終形態のcodepenを貼りますね。

See the Pen Javascript Snippet by DJ Yorozuya (@dj-yorozuya) on CodePen.

Headerメニュー開閉

さて、今回の内容はみなさんおなじみのハンバーガーメニュー
「三」をクリックしたら「×」に切り替わってメニューがひょっこり出てくる、いつものアレです。

Headerメニューの作り方

手始めにHeaderの見た目の作り方を説明します。
と言ってもHTMLとCSSのコーディングはできる前提で進めていきますので、ソース貼り付けて何を書いてるかコメント残す程度にしますね
※class名はDJよろずや流BEM記述となるので、別に同じにしなくても大丈夫です。

【注意】
DJよろずや自作reset.cssを使ってる前提なので、環境によっては同じに書いても同じ見た目にならないことがあります。
その時はどこかのreset.cssを読み込んでから見た目を調整してください。

おすすめreset.css

destyle.css/destyle.css at master · nicolas-cusan/destyle.css
Opinionated reset stylesheet that provides a clean slate for styling your html. - nicolas-cusan/destyle.css

では早速作っていきましょう。

HTML

<header class="l-header"> // Header本体(あくまで外枠で位置調整に使用)
  <div class="l-header_wrap"> // Headerの中身を囲む枠
    <h1 class="headerLogo">ロゴ</h1> // Headerロゴ

    <button id="js-menuTrigger" class="headerMenuBtn"> // 「三」の実装
      <span class="headerMenuBtn_line"></span> // 「三」の1本目の線
      <span class="headerMenuBtn_line"></span> // 「三」の2本目の線
      <span class="headerMenuBtn_line"></span> // 「三」の3本目の線
    </button>

    <ul class="headerNav"> // 「三」を押下したときに出てくるメニュー
      <li class="headerNav_item"> // メニューの各リンク
        <a href="#test1" class="headerNav_item-link">SECTION1</a> // リンク先(今回はアンカーリンク)
      </li>
      // 以下、メニューの繰り返し
      <li class="headerNav_item">
        <a href="#test2" class="headerNav_item-link">SECTION2</a>
      </li>
      <li class="headerNav_item">
        <a href="#test3" class="headerNav_item-link">SECTION3</a>
      </li>
      <li class="headerNav_item">
        <a href="" class="headerNav_item-link">DUMMY</a>
      </li>
      <li class="headerNav_item">
        <a href="" class="headerNav_item-link">DUMMY</a>
      </li>
      <li class="headerNav_item">
        <a href="" class="headerNav_item-link">DUMMY</a>
      </li>
    </ul>
  </div>
</header>

SCSS

/* Header */
.l-header {
  position: fixed; // Headerの位置は常にブラウザに対して固定にする
  top: 0; // ブラウザの最上部を起点とする
  left: 0; // ブラウザの左端を起点とする
  width: 100%; // Headerの横幅をブラウザと同じ大きさにする
  z-index: 9999; // z軸は9999(一番上)にする。※99999999999でも別に良い。一番上になる値を入力

  &_wrap {
    position: relative; // 子要素にposition:absoluteがいる場合の相対位置の起点となる
    padding: 0 16px; // 左右の余白は16px
    display: flex; // 子要素を横並びにする
    align-items: center; // 子要素の縦位置を中央揃えにする
    justify-content: space-between; // 子要素の横位置を両端を起点に均等揃えにする
    width: 100%; // コンテンツの横幅をブラウザと同じ大きさにする
    height: 64px; // コンテンツの高さを64pxにする
    background: #58a9ef; // 背景色を青にする

    /* Logo */
    .headerLogo {
      color: #fff; // 文字色を白にする
      font-size: 24px; // 文字の大きさを24pxにする
      font-weight: bold; // 文字の太さを太くする
    }

    /* Menu Btn */
    .headerMenuBtn {
      display: flex; // 子要素を横並びにする
      flex-direction: column; // 子要素を縦並びに変更
      align-items: center; // 子要素の横位置を中央揃えにする
                           // ※flex-direction:columnを書いているため縦位置ではなく横位置になる
      justify-content: space-between; // 子要素の縦位置を上下均等揃えにする
      width: 24px; // 「三」全体の横幅を24pxにする
      height: 16px; // 「三」全体の高さを16pxにする

      &_line {
        display: block; // spanをブロック要素にする
        width: 24px; // 線1本の横幅を24pxにする
        height: 2px; // 線1本の高さを2pxにする
        background: #fff; // 線の色を白にする
        transition: all .5s ease-in-out; // 「三」が「×」になる時のアニメーションを0.5秒で動くようにする
      }
    }

    /* Menu */
    .headerNav {
      position: absolute; // Headerメニューの中身を相対的な位置になるようにする
      top: -100vh; // ブラウザの高さと同じ分、上に移動する(画面内に見えないようにする)
      left: 0; // 左端を起点とする
      z-index: -1; // z軸を-1にする(これを指定しないと、メニューが出るときにHeaderの上を通り過ぎる)
      padding: 20px; // 上下左右の余白を20pxとする
      display: flex; // 子要素を横並びにする
      flex-direction: column; // 子要素を縦並びに変更する
      align-items: center; // 子要素の横位置を中央揃えにする
      width: 100%; // メニュー本体の横幅をブラウザと同じ大きさにする
      max-height: 200px; // メニュー本体の最大の高さは200pxとする
      background: #75d5ff; // メニューの背景色を水色にする
      transition: all .5s ease-in-out; // メニューが出てくるときのアニメーションを0.5秒で動くようにする
      overflow-y: auto; // メニューの中身が高さ200pxを超えた場合、スクロールできるようにする

      &_item {
        width: 100%; // メニュー1つの横幅をブラウザと同じにする
        max-width: 600px; // メニューの横幅の最大の大きさを600pxにする
        color: #fff; // 文字色を白にする
        font-weight: bold; // 文字の太さを太くする
        text-align: center; // 文字の位置を中央寄せにする
        border-bottom: 1px solid #fff; // メニューの下に1pxの白い線を描画する

        &-link {
          padding: 16px 0; // メニューの上下の余白を16pxにする
        }
      }
    }
  }
}

正直コーディングできる人からしたら「わかってるわ!」ってなりますね。
とりあえずHeaderの見た目はできましたね?
それではjQueryの制御を説明していきます。

Headerメニューの制御

$(function () {
  // id[js-menuTrigger]の要素をクリックしたとき
  $('#js-menuTrigger').on('click',function () {
    // もしclass[l-header]の要素がclass[l-header-active]を持っているなら
    if ($('.l-header').hasClass('l-header-active')) {
      // class[l-header]の要素からclass[l-header-active]を削除する
      $('.l-header').removeClass('l-header-active');
      // 要素[body]からclass[body-menuOpen]を削除する
      $('body').removeClass('body-menuOpen');
    //それ以外(class[l-header]の要素がclass[l-header-active]を持っていない)なら
    } else {
      // class[l-header]の要素にclass[l-header-active]を付与する
      $('.l-header').addClass('l-header-active');
      // 要素[body]にclass[body-menuOpen]を付与する
      $('body').addClass('body-menuOpen');
    }
  });
});

ここでいうid[js-menuTrigger]とは、「三」を指します。
「三」をクリックしたときに、Header本体にclass[l-header-active]を持っているかを確認して、持っているならclass[l-header-active]を削除、持っていないならclass[l-header-active]を付与します。

よく、3行でできるハンバーガーメニューのjQueryみたいな記事があって下記のように記述していることがあります。

$(function(){
  // id[js-menuTrigger]の要素をクリックしたとき
  $('#js-menuTrigger').on('click', function(){
    // class[l-header]の要素にclass[l-header-active]をつけたり消したりする
    $('.l-header').toggleClass('l-header-active');
  });
});

別にこれでもいいのですが、自分はあえてこの書き方をしていません。
理由としては、.toggleClassだと、メニューがアクティブだろうがなかろうが、「三」をクリックしたタイミングでつけたり消したりを繰り返します。
※「三」クリック1回目は付与する、2回目は削除、3回目は付与、4回目は……..

すごいイレギュラーなパターンですが、「三」を超高速でタップした場合、処理がうまく走らなくてclass[l-header-active]を複数回つけようとしたり削除しようとしてしまいます。
「三」のままなのにメニューが出てるとか、その逆も然り。

それを防ぐために
if ($(‘.l-header’).hasClass(‘l-header-active’)) {}
でヘッダーがアクティブかどうかを判断しています。

上記のjQueryの記述だけでは、bodyとheaderにclassを付与しているだけなので、まだ動き自体は実装できてません。
そのため、先ほどのCSSに下記を追記します。

html {
  position: relative; // 子要素にposition:absoluteがいる場合の相対位置の起点となる
  min-height: 100%; // htmlの高さは最低でもブラウザと同じ高さとする
}

/* Menu Open Fixed */
.body-menuOpen {
  height: 100%; // bodyの高さをブラウザと同じにする
  overflow: hidden; // ブラウザの高さよりをコンテンツが長い場合、見えないようにする
}

/* Menu Open Hamburger */
.l-header-active {

  .l-header_wrap {

    .headerMenuBtn_line:nth-child(1) {
      transform: translateY(7px) rotate(-45deg); // 「三」の1本目の線をy軸に7px、そして-45度回転する
    }

    .headerMenuBtn_line:nth-child(2) {
      opacity: 0; // 「三」の2本目の線を透明にする
    }

    .headerMenuBtn_line:nth-child(3) {
      transform: translateY(-7px) rotate(45deg); // 「三」の1本目の線をy軸に-7px、そして45度回転する
    }

    .headerNav {
      top: 64px; // -100vhの位置にあったHeaderメニューを上から64pxに位置に移動する
                 // ※64pxはHeaderと同じ高さ。Headerにかぶらないようにしている
    }
  }
}

はい、このCSSを記述したことで、メニューを開いた時にbodyとheaderにclassが付与され、それぞれのアニメーションが実装されました。

そして気になる部分のhtmlとbodyに記述した理由です。
「なんでheaderに関係ないところにも書くの?」と思った人もいるかもしれないです。

html {
  position: relative; // 子要素にposition:absoluteがいる場合の相対位置の起点となる
  min-height: 100%; // htmlの高さは最低でもブラウザと同じ高さとする
}

/* Menu Open Fixed */
.body-menuOpen {
  height: 100%; // bodyの高さをブラウザと同じにする
  overflow: hidden; // ブラウザの高さよりをコンテンツが長い場合、見えないようにする
}

実はこれ、Headerメニューにスクロールが発生している場合、スクロールしたときにメニュー裏に見えるコンテンツも一緒にスクロールしてしまうのを防ぐために書いています。

もしこれらを書かなかった場合、

こんな風に後ろもスクロールしてしまいます。

おわりに

駆け足でしたが完成しましたかね?

jQueryは書き方さえわかってしまえば自分の好きなように何でもできます。
もちろんその書き方は効率的なのか、処理が重くないかは判断しかねますが、どんどん書いてっていいものができるように頑張っていきましょう。

それではまた👋