JavaScriptを少しだけ ―
インタラクションを加えよう
完成したcafé SOELのサイトに、操作への反応を加えます。ハンバーガーメニューの開閉、ARIA属性の更新、スクロール連動アニメーションまで、最小限のJavaScriptで整えていきましょう。

本編と前回のおさらい
- 本編全10回で、café SOELのWebサイトを公開できる状態まで作り上げました
- 番外編第1回では、CSSアニメーションと fade-in-up クラスを追加しました
- スクロール連動の動きには、JavaScriptで is-visible クラスを付ける必要がありました
本編の最後で、あなたのサイトは世界に公開できる状態になりました。 そして前回、CSSだけで「どう動くか」の準備をしましたね。
今回は、その準備に命を吹き込みます。 JavaScriptは、HTMLとCSSで作った画面に「反応」を与える言語です。 たとえば、ボタンを押したらメニューが開く。 スクロールしたら要素がふわっと現れる。 そういう小さな体験を担当します。
- js/main.js を作成し、HTMLから読み込む
- ハンバーガーメニューをクリックで開閉できるようにする
- aria-expanded をJavaScriptで更新し、状態を正しく伝える
- ナビリンクをクリックしたら、モバイルメニューを閉じる
- 前回準備したスクロール連動アニメーションを動かす
JavaScriptの役割を、まず小さくつかむ
HTMLは構造、CSSは見た目。 ではJavaScriptは何でしょう。 私はよく「照明のスイッチ」に例えます。
部屋そのものを作るのがHTML。 壁紙や家具を整えるのがCSS。 そして、スイッチを押したら明かりがつく。 その「操作に反応する部分」がJavaScriptです。
JavaScriptは、ページに命令する言語。大事なのは、最初から大きく書こうとしないこと。
今回使う考え方は、たった3つです。
要素を見つける
document.querySelector() で、HTMLの中から操作したい要素を探します。
イベントを待つ
addEventListener() で、「クリックされたら」などの操作を待ちます。
クラスや属性を変える
classList.toggle() や setAttribute() で、見た目や状態を切り替えます。
js/main.jsを作ってHTMLから読み込む
まずはJavaScriptを書くファイルを用意します。 プロジェクトの中に js フォルダを作り、 その中に main.js を作成してください。
my-website/
├── index.html
├── css/
│ └── style.css
├── img/
│ └── ...
└── js/
└── main.js
次に、index.html からこのJavaScriptファイルを読み込みます。 場所は </body> の直前でも動きますが、 今回は現場でもよく使う defer 付きの読み込みにしましょう。
<!-- ↓ CSSのlinkタグの近くに追加 -->
<script src="js/main.js" defer></script>
defer は、「HTMLを読み終わってからJavaScriptを実行してね」という指定です。 これがあると、JavaScriptがHTMLより先に動いてしまって 「探したい要素がまだ存在しない」という事故を防げます。
ハンバーガーメニューをクリックで開閉する
ここから、いよいよJavaScriptを書いていきます。 まず操作したい要素は2つ。 メニューボタンと、ナビゲーションです。
// ハンバーガーメニューの要素を取得
const menuToggle = document.querySelector('.menu-toggle');
const nav = document.querySelector('.nav');
querySelector() は、CSSセレクタと同じ書き方で要素を探せます。 '.menu-toggle' なら、class名が menu-toggle の要素。 '.nav' なら、class名が nav の要素です。
<nav class="nav" aria-label="メインナビゲーション">
<!-- nav-list が入る -->
</nav>
次に、ボタンがクリックされたら is-open クラスを付け外しします。 第6回・第7回でCSS側にはすでに、 .nav.is-open や .menu-toggle.is-open の見た目を用意していました。
menuToggle.addEventListener('click', () => {
menuToggle.classList.toggle('is-open');
nav.classList.toggle('is-open');
});
保存して、ブラウザで確認してみましょう。 画面幅を狭くしてハンバーガーメニューをクリックすると、 メニューが開閉するはずです。
aria-expandedも一緒に更新する
メニューは開閉できるようになりました。 でも、ここで大事なことがあります。 見た目だけでなく、「今メニューが開いているかどうか」を 支援技術にも伝えること。
そのために使うのが aria-expanded です。 false なら閉じている。 true なら開いている。 ボタンの状態を伝える属性です。
<button
class="menu-toggle"
type="button"
aria-label="メニューを開く"
aria-expanded="false"
>
<span class="menu-toggle-bar"></span>
<span class="menu-toggle-bar"></span>
<span class="menu-toggle-bar"></span>
</button>
JavaScript側では、クラスを切り替えたあとに 「今開いているか」を調べます。 そして、その結果を aria-expanded に反映します。
menuToggle.addEventListener('click', () => {
menuToggle.classList.toggle('is-open');
nav.classList.toggle('is-open');
// メニューが開いているかどうかを判定
const isOpen = menuToggle.classList.contains('is-open');
// 状態をARIA属性に反映
menuToggle.setAttribute('aria-expanded', isOpen);
menuToggle.setAttribute(
'aria-label',
isOpen ? 'メニューを閉じる' : 'メニューを開く'
);
});
アクセシビリティは、あとから飾るものではなく、動きを作る瞬間に一緒に設計するもの。
ナビリンクをクリックしたらメニューを閉じる
モバイルメニューでは、リンクを押したあともメニューが開いたままだと、 画面がふさがってしまいます。 そこで、ナビリンクをクリックしたら自動で閉じる処理を追加します。
// ナビリンクをすべて取得
const navLinks = document.querySelectorAll('.nav-link');
querySelectorAll() は、条件に合う要素をすべて取得します。 今回なら、Concept、Menu、Gallery、Accessの4つのリンクですね。
navLinks.forEach((link) => {
link.addEventListener('click', () => {
menuToggle.classList.remove('is-open');
nav.classList.remove('is-open');
menuToggle.setAttribute('aria-expanded', 'false');
menuToggle.setAttribute('aria-label', 'メニューを開く');
});
});
ここでは toggle() ではなく remove() を使っています。 理由は、「クリックしたら必ず閉じたい」から。 状態を反転させるのではなく、閉じた状態に固定するわけです。
スクロール連動アニメーションを動かす
番外編第1回で、私たちは fade-in-up と is-visible のCSSを用意しました。 でもCSSだけでは、「画面に入ったらクラスを付ける」という判断はできません。
そこで使うのが IntersectionObserver です。 これは、要素が画面内に入ったかどうかを監視するための仕組み。 スクロール連動アニメーションでは、とてもよく使われます。
// スクロール連動アニメーション
const fadeElements = document.querySelectorAll('.fade-in-up');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.15
});
fadeElements.forEach((element) => {
observer.observe(element);
});
threshold: 0.15 は、 要素が15%見えたら反応する、という意味です。 早すぎず、遅すぎず。 スクロール演出では扱いやすい値です。
observer.unobserve(entry.target) は、 一度表示された要素の監視をやめるための処理です。 これを入れておくと、同じアニメーションが何度も再生されず、 パフォーマンスにもやさしくなります。
ここまでのmain.js全体
では、今回書いたJavaScriptをひとつにまとめます。 うまく動かないときは、まずこのコードと見比べてみてください。
// ===================================
// café SOEL — Main JavaScript
// 番外編②:インタラクション
// ===================================
// ---------- ハンバーガーメニュー ----------
const menuToggle = document.querySelector('.menu-toggle');
const nav = document.querySelector('.nav');
const navLinks = document.querySelectorAll('.nav-link');
if (menuToggle && nav) {
menuToggle.addEventListener('click', () => {
menuToggle.classList.toggle('is-open');
nav.classList.toggle('is-open');
const isOpen = menuToggle.classList.contains('is-open');
menuToggle.setAttribute('aria-expanded', isOpen);
menuToggle.setAttribute(
'aria-label',
isOpen ? 'メニューを閉じる' : 'メニューを開く'
);
});
navLinks.forEach((link) => {
link.addEventListener('click', () => {
menuToggle.classList.remove('is-open');
nav.classList.remove('is-open');
menuToggle.setAttribute('aria-expanded', 'false');
menuToggle.setAttribute('aria-label', 'メニューを開く');
});
});
}
// ---------- スクロール連動アニメーション ----------
const fadeElements = document.querySelectorAll('.fade-in-up');
if (fadeElements.length > 0) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.15
});
fadeElements.forEach((element) => {
observer.observe(element);
});
}
if (menuToggle && nav) のような条件を入れているのは、 要素が見つからなかったときにエラーで止まらないようにするためです。 小さな保険ですが、ページが増えたときにも安心です。
今回のまとめ
- JavaScriptは、HTMLとCSSで作った画面に「操作への反応」を加えるための言語です
- querySelector() で要素を探し、addEventListener() で操作を待ちます
- classList.toggle() を使うと、クリックに合わせてクラスを付け外しできます
- aria-expanded は、メニューが開いているか閉じているかを支援技術へ伝える大切な属性です
- ナビリンクをクリックした後は、remove() で必ず閉じた状態にすると扱いやすくなります
- IntersectionObserver を使うと、要素が画面に入ったタイミングでアニメーションを開始できます
- ここまでできたなら、静的なサイトに小さなインタラクションを加える基礎はもう身についています