JavaScriptのIntersectionObserverでメニューアイテムを動的にハイライトする方法
JavaScriptのIntersectionObserverでメニューアイテムを動的にハイライトする方法を紹介します。
目次から読む
JavaScriptのIntersectionObserverでメニューアイテムを動的にハイライトする手順
- ScrollObserverクラスを作成してIntersectionObserverを初期化
- Mainクラスを作成してScrollObserverクラスを活用
ScrollObserverクラスを作成してIntersectionObserverを初期化
class ScrollObserver {
constructor(els, cb, options) {
this.els = document.querySelectorAll(els);
const defaultOptions = {
root: null,
rootMargin: '0px',
threshold: 0,
once: true,
};
this.cb = cb;
this.options = Object.assign(defaultOptions, options);
this.once = this.options.once;
this._init();
}
_init() {
const callback = function (entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.cb(entry.target, true);
if (this.once) {
observer.unobserve(entry.target);
}
} else {
this.cb(entry.target, false);
}
});
};
this.io = new IntersectionObserver(callback.bind(this), this.options);
this.els.forEach(el => this.io.observe(el));
}
destroy() {
this.io.disconnect();
}
}
html
<header class="header">
<nav class="header__nav">
<img class="header__logo" src="../src/img/logo.png" alt="" />
<div class="header__nav-container">
<ul class="header__nav-list">
<li class="header__nav-item">
<a href="#home" class="header__nav-link">Home</a>
</li>
<li class="header__nav-item">
<a href="#fields" class="header__nav-link">Science Fields</a>
</li>
<li class="header__nav-item">
<a href="#courses" class="header__nav-link">Course Types</a>
</li>
<li class="header__nav-item">
<a href="#facilities" class="header__nav-link">Facilities</a>
</li>
<li class="header__nav-item">
<a href="#awards" class="header__nav-link">Awards</a>
</li>
<li class="header__nav-item">
<a href="#branches" class="header__nav-link">Branches</a>
</li>
<li class="header__nav-item">
<a href="#contact" class="header__nav-link">Contact Us</a>
</li>
</ul>
<div class="sns">
<a href="#" class="sns__link"><i class="fab fa-facebook-f"></i></a>
<a href="#" class="sns__link"><i class="fab fa-twitter"></i></a>
<a href="#" class="sns__link"><i class="fab fa-github"></i></a>
</div>
</div>
<button class="header__mobile-menu">
<span class="header__line"> </span>
</button>
</nav>
</header>
<section class="section-hero" id="home">
...
<section class="section-fields" id="fields">
...
Mainクラスを作成してScrollObserverクラスを活用
document.addEventListener('DOMContentLoaded', () => {
const main = new Main();
});
class Main {
constructor() {
this.sections = [
'.section-hero',
'.section-fields',
'.section-courses',
'.section-facilities',
'.section-awards',
'.section-branches',
'.section-contact',
];
this.navItems = document.querySelectorAll('.header__nav-item');
this._observers = [];
this._init();
}
set observers(val) {
this._observers.push(val);
}
get observers() {
return this._observers;
}
_init() {
this._scrollInit();
}
// ビュー内のsectionのメニューアイテムをハイライト
_highlightMenuItem(el, inview) {
// elはインスタンス化したScrollObserverのsection、inviewはintersectingのboolean
this.navItems.forEach(item => {
const itemHref = item.childNodes[0].getAttribute('href');
const activeSection = el.getAttribute('id');
if (!inview) return;
if (inview) {
if (activeSection === itemHref.slice(1)) {
item.childNodes[0].classList.add('u-active-item');
} else {
item.childNodes[0].classList.remove('u-active-item');
}
}
});
}
_destroyObservers() {
this.observers.forEach(ob => {
ob.destroy();
});
}
destroy() {
this._destroyObservers();
}
_scrollInit() {
// メニューの現在のアイテムのハイライト
this.sections.forEach(section => {
this.observers = new ScrollObserver(
section,
this._highlightMenuItem.bind(this),
{ once: false, rootMargin: '-50% 0px' }
);
});
}
}
constructor内のsectionsのclass名とナビゲーションメニューのアイテムのclass名は、適宜変更します。
highlightMenuItemというメソッドを作成しています。
elはインスタンス化したScrollObserverのsectionが渡されます。
inviewはintersectingのbooleanです。
navタグ内のアイテムをquerySelectorAllですべて選択し、そのhref属性に指定されている飛び先と、sectionのidと合致するものにactiveとわかるクラスを付与します。
交差は、inviewで判定します。
各sectionのidは、elからアクセスすることができます。
そのために、sectionsでそれぞれのクラスなりを格納して、ScrollObserverを初期化するときに、渡せるようにします。