【脱jQuery】jQuery → JavaScript (VanillaJS) 書き換え(2)コードスニペット(備忘録)
【共通:初期設定・宣言】グローバル変数
'use strict';
// グローバル変数
const html = $('html');
const body = $('body');
const header = $('header'); // (1)
const headerHeight = header.height();
const anchorLink = $('[href*="#"]:not([href*="#!"])'); // (2)
'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タグが使われているソースコードを、今のところはあまり見たことがないが)
(2)anchorLink
そのページにおける「ページ内リンク(id="〇〇" の箇所へのリンク)」が設定されたタグ(複数のaタグ想定、href属性に#が含まれていれば条件一致、href="#!"は除く)を配列で取得。
基本的にaタグのみを想定しているが、念のため属性セレクタのみの指定を活用。
なお、別途プラグイン(ポップアップ等)との兼ね合いで「これは【#】使うが対象からは外したい」という場合は、使用する文字列(例:js-popup-01, js-popup-02,...)を決めて:not([href*="〇〇"]により追加で除外すればOK
(例)[href*="#"]:not([href*="#!"]:not([href*="popup"])
目次
- 共通設定
- スクロール判断
- スムーススクロール
- アンカーリンクへのページロードアクセス時の位置調整
- ハンバーガーメニュー
- ドロップダウンメニュー
- タブ切り替え(執筆途中)
- トグル・アコーディオンメニュー(執筆途中)
- ブラウザの判断(執筆途中)
- モーダルウィンドウ(執筆途中)
【共通設定1】href="#!"のリンク無効化
// href="#!"のリンク無効化
$('[href*="#!"]').on('click', function() {
return false;
});
// 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)
$(window).on('scroll', function () {
if ($(this).scrollTop() < headerHeight) {
body.removeClass('is-scrolled');
} else {
body.addClass('is-scrolled');
}
});
// スクロールの判断(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)
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 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で対応したいところ。
html {
scroll-behavior: smooth;
scroll-padding-top: 100px; /* ヘッダーの高さが100pxの場合 */
}
アンカーリンクへのページロードアクセス時の位置調整
内容
- あるページから「別のページのアンカーリンクの箇所」へアクセスした際に、本来は上記スムーススクロールの項で述べているような固定ヘッダー分の位置のズレが生じ得る
→ これを防止するべく位置調整する。 - 基本的にはスムーススクロール時の『スクロール位置の調整』と同じ
- scrollToメソッドにおいて behavior: 'smooth', を指定した場合
→ ページのロードが終わってからスムーススクロールの挙動(普通は不自然)
→ behavior: 'auto', が「一瞬で移動」に該当するため自然。ただしこれは初期値なので省略可能。 - 上記スムーススクロールの項と同様、適宜、数値指定で位置の調整(量の増減)は可能
メソッド解説
- 文字列.indexOf('#') !== -1
→ 返り値が-1でない
→ 文字列に # を含む場合
// 別ページからアンカーリンクへのアクセス時にヘッダー分の高さを引くことで位置の調整(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 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)
$('#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 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タグには「メニュー展開時」に以下のスタイル指定をして、展開時の裏(背景)でのスクロールを固定する。
.is-navopen {
height: 100%;
overflow: hidden;
}
ドロップダウンメニュー(複数想定)
ホバー時に該当する要素(+ ページ全体)へのクラス名の付け外し
- メニューボタンのタグに必要な属性:class="js-dropdown"
→ bodyタグに is-dropdown-hover-bodyクラス、ホバーした要素に is-dropdown-hoverクラス、
以上の付け外し - メニュー展開時には、ページ全体や各所で操作したい箇所がある場合も多々。
よってbodyタグにも同時にクラス追加イベント実施。
// ドロップダウンメニュー(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 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();