Open8

【脱jQuery】jQuery → JavaScript (VanillaJS) 書き換え(2)コードスニペット(備忘録)

メイハクメイハク

【共通:初期設定・宣言】グローバル変数

jQuery-script
'use strict';

// グローバル変数
const html = $('html');
const body = $('body');
const header = $('header'); // (1)
const headerHeight = header.height();
const anchorLink = $('[href*="#"]:not([href*="#!"])'); // (2)
Vanilla-js
'use strict';

// グローバル変数
const html = document.documentElement;
const body = document.body;
const header = document.querySelector('header'); // (1)
const headerHeight = header.offsetHeight;
const anchorLink = [...document.querySelectorAll('[href*="#"]:not([href*="#!"])')]; // (2)

詳細解説

(1)header = グローバルナビ

そのページにおける「グローバルナビゲーション(ヘッダーメニュー)全体に該当するタグ」を取得。

(注意点)

同一ページ内に「headerタグ」が複数ある状況(※)を懸念する場合は、それがグロナビであるとわかるようなクラス名 or ID名(1ページ内に1つ想定)をHTMLへ指定した上で、そのクラスを取得するように。
(例)CSS設計にFLOCSSを導入している場合、<header class="l-header">タグは共通のパーツとして各ページに1つ想定
→ const header = document.querySelector('.l-header');
で問題なく該当タグを取得可能。

(※)マークアップの観点から見れば、headerタグやfooterタグは各コンテンツのグループ毎に、つまり1ページ内に複数使っても問題ない。
(私の経験上、1ページ内に複数のheader / footerタグが使われているソースコードを、今のところはあまり見たことがないが)

そのページにおける「ページ内リンク(id="〇〇" の箇所へのリンク)」が設定されたタグ(複数のaタグ想定、href属性に#が含まれていれば条件一致、href="#!"は除く)を配列で取得。
基本的にaタグのみを想定しているが、念のため属性セレクタのみの指定を活用。

なお、別途プラグイン(ポップアップ等)との兼ね合いで「これは【#】使うが対象からは外したい」という場合は、使用する文字列(例:js-popup-01, js-popup-02,...)を決めて:not([href*="〇〇"]により追加で除外すればOK
(例)[href*="#"]:not([href*="#!"]:not([href*="popup"])

メイハクメイハク

目次

  • 共通設定
  • スクロール判断
  • スムーススクロール
  • アンカーリンクへのページロードアクセス時の位置調整
  • ハンバーガーメニュー
  • ドロップダウンメニュー
  • タブ切り替え(執筆途中)
  • トグル・アコーディオンメニュー(執筆途中)
  • ブラウザの判断(執筆途中)
  • モーダルウィンドウ(執筆途中)
メイハクメイハク

【共通設定1】href="#!"のリンク無効化

jQuery-script
// href="#!"のリンク無効化
$('[href*="#!"]').on('click', function() {
  return false;
});
Vanilla-js
// href="#!"のリンク無効化
function invalidLinkFalse() {
  const invalidLink = [...document.querySelectorAll('[href*="#!"]')];
  for ( let i = 0; i < invalidLink.length; i++ ) {
    invalidLink[i].addEventListener('click', (e) => {
      e.preventDefault();
    });
  }
}
invalidLinkFalse();
メイハクメイハク

スクロールの判断

ページ上部から一定量のスクロールがあった際の処理

  • スクロールが一定量を超えたらクラス名を追加/下回ったらクラス名を削除
  • イベントの対象はbodyタグを指定。

用途

ページ上部から『ヘッダーの高さ』以上のスクロールがあったら、ヘッダーをページ上部に固定したい、といった対処を目的としている。
よってしきい値となるスクロール量 = ヘッダー分の高さを基本設定としているが、状況に応じて変更可能。
(例)ページ上部から少しでもスクロールがあったら処理をしたい
→ headerHieghtのグローバル変数部分を「1」にする。

jQuery-script
// スクロールの判断(jQuery)
$(window).on('scroll', function () {
  if ($(this).scrollTop() < headerHeight) {
    body.removeClass('is-scrolled');
  } else {
    body.addClass('is-scrolled');
  }
});
Vanilla-js
// スクロールの判断(Vanilla JavaScript)
function scrollJudge(parameter = headerHeight) {
  const activeClass = 'is-scrolled';

  window.addEventListener('scroll', function() {
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    if (scrollTop < parameter) {
      body.classList.remove(activeClass);
    } else {
      body.classList.add(activeClass);
    }
  });
}
scrollJudge();
メイハクメイハク

スムーススクロール

内容

  • 同ページ内でアンカーリンクの箇所へ移動する際に、滑らかなアニメーションのスクロール動作を実装

詳細

  • #から始まるaタグ(href="#〇〇")のみをイベント発生の対象としており、「別ページへの遷移」が含まれたアンカーリンクはイベントの対象ではない(例:href="/about/#link01" 等は対象外 ※1)
  • href="#" ないしは href="#top" をクリックした場合 → ページ上端へ移動
    同じ機能を持つアンカーリンクを増やしたい場合は、以下で適宜追加可能。
    || href === '#hoge'
  • jQuery依存の場合は遷移スピードを簡単に調整可能だが、VanillaJSの場合は工数が多く、そこまでを完全自作するのはあまり現実的ではない(実務でもjQuery依存なしでそこまでの実装を求められる機会は、経験上あまりない)
    → 必要な場合は「jQuery依存ではない、そういったJSプラグイン」を使った方がもはや手っ取り早いかもしれない(スライダー実装におけるSwiper 等と同様に)

スクロール位置の調整

そのままスクロール移動するとデバイス(モニター)やブラウザウィンドウの上端が移動の終着地点となる。
→ (あるある)ヘッダーが画面上部に固定されている場合、その分と被ってしまい違和感があるため調整が必要。
→ 本来の終着地点から「ページ上部に固定されている前提のヘッダー分の高さ」を引いた位置に終着するように調整。

この高さは状況に応じて変更可

  • ピクセル(絶対数値)で指定したい場合
    → headerHieghtのグローバル変数部分をその値(単位なし)にする
  • headerHeight以上(+ 10pxなど)の高さを指定したい場合
    → headerHieghtのグローバル変数部分に +10 など加算
  • この処理が不要な場合
    → headerHieghtのグローバル変数部分を「0」にする(もしくは該当部分を丸々削除)
jQuery-script
// スムーススクロール(jQuery)
anchorLink.on('click', function() {
  const href = $(this).attr('href');
  const target = $(href === '#' || href === '#top' ? body : href);
  const position = target.offset().top - headerHeight; //ヘッダー分の高さを引くことで、スクロール位置を調整
  const speed = 400; // 1000 = 1秒でスピード設定
  $(html, body).animate({scrollTop: position}, speed, 'swing');
  return false;
});
Vanilla-js
// スムーススクロール(Vanilla JavaScript)
function smoothScroll(parameter = headerHeight) {
  for ( let i = 0; i < anchorLink.length; i++ ) {
    anchorLink[i].addEventListener('click', (e) => {
      const href = anchorLink[i].getAttribute('href'); // 各aタグのリンク先を取得
      const hrefFirstLetter = href.charAt(0); // 1文字目を取得

      if(hrefFirstLetter === '#') { // 1文字目が # から始まる場合
        e.preventDefault(); // リンク無効化(デフォルトのイベントをブロック)
        if(href === '#' || href === '#top') {
          window.scrollTo({ //スムーススクロールの実施
            top: 0,
            behavior: 'smooth',
          });
        } else {
          const targetElement = document.getElementById(href.replace('#', '')); // リンク先の要素(コンテンツ)をページ内から探して取得
          const rect = targetElement.getBoundingClientRect().top; // ブラウザからの高さを取得
          const offset = window.pageYOffset; // 現在のスクロール量を取得
          const gap = parameter; // 位置調整のための差分:通常は、固定のヘッダー(グローバルナビ)の分の高さ
          const target = rect + offset - gap; // 最終的な位置を割り出す

          window.scrollTo({ //スムーススクロールの実施
            top: target,
            behavior: 'smooth',
          });
        }
      }
    });
  }
}
smoothScroll();

補足

  • (※ 1)以前は、以下のように「href属性が#から始まるタグ」をグローバル変数:ancherLinkとは別に宣言してそれを活用していた。
    const smoothScrollTrigger = [...document.querySelectorAll('[href^="#"]:not([href*="#!"])')];
    しかし、それもまたややこしくなるので、上述の通り「アンカーリンクの中から#から始まるもののみ」にイベントが発生するような仕様へと変更した。
  • もはやスムーススクロールは次のCSSで実装できるのでは?という意見もあり、確かにその通りではあるが、ロードする度にスムース的挙動があったりなど、CSSだけで完結させるのに未だ少し不安があるため、今しばらくはJSで対応したいところ。
smooth-scroll.css
html {
  scroll-behavior: smooth;
  scroll-padding-top: 100px; /* ヘッダーの高さが100pxの場合 */
}
メイハクメイハク

アンカーリンクへのページロードアクセス時の位置調整

内容

  • あるページから「別のページのアンカーリンクの箇所」へアクセスした際に、本来は上記スムーススクロールの項で述べているような固定ヘッダー分の位置のズレが生じ得る
    これを防止するべく位置調整する。
  • 基本的にはスムーススクロール時の『スクロール位置の調整』と同じ
  • scrollToメソッドにおいて behavior: 'smooth', を指定した場合
    → ページのロードが終わってからスムーススクロールの挙動(普通は不自然)
    → behavior: 'auto', が「一瞬で移動」に該当するため自然。ただしこれは初期値なので省略可能。
  • 上記スムーススクロールの項と同様、適宜、数値指定で位置の調整(量の増減)は可能

メソッド解説

  • 文字列.indexOf('#') !== -1
    → 返り値が-1でない
    → 文字列に # を含む場合
jQuery-script
// 別ページからアンカーリンクへのアクセス時にヘッダー分の高さを引くことで位置の調整(jQuery)
$(window).on('load', function () {
  const url = $(location).attr('href');
  if (url.indexOf('#') !== -1) {
    const anchor = url.split('#');
    const target = $('#' + anchor[anchor.length - 1]);
    if (target.length > 0) {
      const position = Math.floor(target.offset().top) - headerHeight; //ヘッダー分の高さを引くことで、スクロール位置を調整
      const speed = 0; // 1000 = 1秒でスピード設定。0で即移動
      $(html, body).animate({scrollTop: position}, speed);
    }
  }
});
Vanilla-js
// 別ページからアンカーリンクへのアクセス時にヘッダー分の高さを引くことで位置の調整(Vanilla JavaScript)
function AccessAnchorLinkPositionAdjust(parameter = headerHeight) {
  window.addEventListener('load', function() {
    const url = location.href;
    if (url.indexOf('#') !== -1) {
      const anchor = url.split('#');
      const href = '#' + anchor[anchor.length - 1];
      if (href.length > 0) {
        const targetElement = document.getElementById(href.replace('#', ''));
        const rect = targetElement.getBoundingClientRect().top;
        const offset = window.pageYOffset;
        const gap = parameter; // 固定ヘッダー分の高さ
        const target = rect + offset - gap; //最終的な位置を割り出す

        window.scrollTo({
          top: target,
        });
      }
    }
  });
}
AccessAnchorLinkPositionAdjust();
メイハクメイハク

ハンバーガーメニュー(1ページ内に1個想定)

クリックイベントによる単純なクラス名の付け外し

  • メニューボタンのタグに必要な属性:id="js-nav-trigger"
    → クリックにより、bodyタグに is-navopenクラスの付け外し
  • メニュー展開時には、ページ全体や各所で操作したい箇所がある場合も多々。
    よってイベントの対象はbodyタグを指定。
  • toggle系のメソッドは(現状)できうる限り使っていない。→ 以後、改修する可能性はあり。
    • 意図せぬ挙動(予期せぬエラー)を防ぐ。
    • 多少長くても、読んだときにちゃんと意味がわかるように。

anchorLink(ページ内リンク)への処理

ハンバーガーメニュー展開時にアンカーリンク(#から始まっている/いないにかかわらず、href属性に#が含まれているaタグ)がクリック(タップ)された際、メニューが自動で閉じるように

jQuery-script
// ハンバーガーメニュー(jQuery)
$('#js-nav-trigger').on('click', function() {
  if (body.hasClass('is-navopen')) {
    body.removeClass('is-navopen');
  } else {
    body.addClass('is-navopen');
  }
});

anchorLink.on('click', function() {
  body.removeClass('is-navopen');
});
Vanilla-js
// ハンバーガーメニュー(Vanilla JavaScript)
function hamburger() {
  const activeClass = 'is-navopen';
  const trigger = document.querySelector('#js-nav-trigger');

  trigger.addEventListener('click', function() {
    if (body.classList.contains(activeClass)) {
      body.classList.remove(activeClass);
    } else {
      body.classList.add(activeClass);
    }
  });

  for ( let i = 0; i < anchorLink.length; i++ ) {
    anchorLink[i].addEventListener('click', function() {
      body.classList.remove(activeClass);
    });
  }
}
hamburger();

 

補足・注意事項

CSS

bodyタグ or HTMLタグには「メニュー展開時」に以下のスタイル指定をして、展開時の裏(背景)でのスクロールを固定する。

CSS
.is-navopen {
  height: 100%;
  overflow: hidden;
}
メイハクメイハク

ドロップダウンメニュー(複数想定)

ホバー時に該当する要素(+ ページ全体)へのクラス名の付け外し

  • メニューボタンのタグに必要な属性:class="js-dropdown"
    → bodyタグに is-dropdown-hover-bodyクラス、ホバーした要素に is-dropdown-hoverクラス、
    以上の付け外し
  • メニュー展開時には、ページ全体や各所で操作したい箇所がある場合も多々。
    よってbodyタグにも同時にクラス追加イベント実施。
jQuery-script
// ドロップダウンメニュー(jQuery)
$('.js-dropdown').hover(
  function () {
    body.addClass('is-dropdown-hover-body');
    $(this).addClass('is-dropdown-hover');
  },
  function () {
    body.removeClass('is-dropdown-hover-body');
    $(this).removeClass('is-dropdown-hover');
  },
);
Vanilla-js
// ドロップダウンメニュー(Vanilla JavaScript)
function dropdown() {
  const activeClass01 = 'is-dropdown-hover-body';
  const activeClass02 = 'is-dropdown-hover';
  const trigger = [...document.querySelectorAll('.js-dropdown')]; // 配列で取得

  trigger.forEach(function(item) {
    item.addEventListener('mouseenter', function() {
      body.classList.add(activeClass01);
      item.classList.add(activeClass02);
    },false);
    item.addEventListener('mouseleave', function() {
      body.classList.remove(activeClass01);
      item.classList.remove(activeClass02);
    },false);
  });
}
dropdown();