この章では、保守しやすいJavaScriptの書き方を学びます。
ここまでの章で、ハンバーガーメニュー、アコーディオン、タブ、フォーム、スクロール演出、スライダーなどを扱ってきました。
実務では、それらのコードを1つのファイルにただ並べるだけだと、すぐに読みづらくなります。後から修正しづらくなり、別ページでエラーが出たり、同じ処理を何度もコピペしたりする原因になります。
この章では、案件で使い回しやすく、壊れにくいJavaScriptを書くための考え方を整理します。
汚いJavaScriptになりやすい理由
Web制作のJavaScriptは、最初は小さく始まります。
const button = document.querySelector(".js-button");
const menu = document.querySelector(".js-menu");
button.addEventListener("click", () => {
menu.classList.toggle("is-active");
});しかし、案件が進むと処理が増えていきます。
- ハンバーガーメニュー
- アコーディオン
- タブ
- モーダル
- フォーム
- スライダー
- スクロール演出
これらを整理せずに1つのファイルへ書き続けると、どこに何があるのかわからなくなります。
保守しやすくするには、処理の置き場所と責務を決めることが大切です。
ファイル設計の基本
小さなサイトなら、main.jsだけでも問題ありません。
ただし、処理が増えてきたら、UIごとにファイルを分けると読みやすくなります。
assets/js/
main.js
modules/
menu.js
accordion.js
tabs.js
modal.js
slider.js
form.jsmain.jsは、各モジュールを呼び出す入口にします。
import { initMenu } from "./modules/menu.js";
import { initAccordion } from "./modules/accordion.js";
import { initTabs } from "./modules/tabs.js";
import { initSlider } from "./modules/slider.js";
initMenu();
initAccordion();
initTabs();
initSlider();各ファイルには、そのUIの処理だけを書きます。
export function initAccordion() {
const accordions = document.querySelectorAll(".js-accordion");
accordions.forEach((accordion) => {
const button = accordion.querySelector(".js-accordion-button");
const panel = accordion.querySelector(".js-accordion-panel");
if (!button || !panel) {
return;
}
button.addEventListener("click", () => {
const isOpen = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!isOpen));
panel.hidden = isOpen;
});
});
}初期化関数
UIごとに、初期化関数を作ると管理しやすくなります。
function initModal() {
const openButtons = document.querySelectorAll(".js-modal-open");
if (openButtons.length === 0) {
return;
}
// モーダルの処理
}
initModal();関数名は、initMenu、initAccordion、initModalのように、何を初期化するかがわかる名前にします。
対象要素がない時のガード
複数ページで同じJavaScriptを読み込む場合、すべてのページに対象要素があるとは限りません。
対象要素がない時は、何もしないようにします。
export function initTabs() {
const tabGroups = document.querySelectorAll(".js-tabs");
if (tabGroups.length === 0) {
return;
}
tabGroups.forEach((tabGroup) => {
// タブ処理
});
}querySelectorAllは、対象がなくても空のNodeListを返します。
length === 0で対象がないことを確認できます。
複数設置に対応する
UIは、最初は1つだけでも後から増えることがあります。
アコーディオンやタブ、モーダル、スライダーは複数設置を想定しておくと安全です。
const accordions = document.querySelectorAll(".js-accordion");
accordions.forEach((accordion) => {
const button = accordion.querySelector(".js-accordion-button");
const panel = accordion.querySelector(".js-accordion-panel");
if (!button || !panel) {
return;
}
button.addEventListener("click", () => {
panel.hidden = !panel.hidden;
});
});ポイントは、各コンポーネントの中から要素を探すことです。
const button = accordion.querySelector(".js-accordion-button");
const panel = accordion.querySelector(".js-accordion-panel");ページ全体から探すと、別のアコーディオンの要素を操作してしまうことがあります。
CSS用クラスとJS用クラスを分ける
CSS用クラスとJavaScript用クラスは、分けておくと壊れにくくなります。
<button class="button button-primary js-modal-open" type="button">
モーダルを開く
</button>buttonやbutton-primaryはCSS用です。
js-modal-openはJavaScriptで取得するためのクラスです。
デザイン修正でCSSクラスが変わっても、js-クラスが残っていればJavaScriptは動き続けます。
状態クラス
状態を表すクラスには、is-やhas-を使うことが多いです。
<nav class="global-menu is-active"></nav>よく使う状態クラスの例です。
| クラス | 意味 |
|---|---|
is-active | アクティブ |
is-open | 開いている |
is-visible | 表示されている |
is-hidden | 隠れている |
is-current | 現在位置 |
has-error | エラーがある |
プロジェクト内でルールを決めておくと、CSSとJavaScriptの連携が読みやすくなります。
data属性で設定を渡す
同じUIでも、場所によって設定を変えたいことがあります。
たとえば、モーダルを開くボタンがどのモーダルを開くかを、data-*属性で持たせます。
<button class="js-modal-open" type="button" data-modal-target="modal-contact">
お問い合わせを開く
</button>
<div class="modal js-modal" id="modal-contact" hidden></div>const targetId = button.dataset.modalTarget;
const modal = document.getElementById(targetId);data-*属性を使うと、JavaScriptの中にIDや設定値をベタ書きしすぎずに済みます。
設定オブジェクト
複数の設定値をまとめたい時は、オブジェクトを使います。
const menuOptions = {
activeClass: "is-active",
openClass: "is-menu-open",
breakpoint: "(max-width: 768px)",
};小さな処理では不要ですが、同じ値が何度も出る場合はまとめると読みやすくなります。
menu.classList.add(menuOptions.activeClass);
document.body.classList.add(menuOptions.openClass);コメントの書き方
コメントは、コードを読めばわかることではなく、判断理由を書くために使います。
// クラスを追加する
menu.classList.add("is-active");これはコードを見ればわかります。
// iOS Safariでは背景スクロールが残るため、body側にも状態を持たせる
document.body.classList.add("is-menu-open");なぜその処理が必要なのかを書くと、後から読む人に役立ちます。
小さな設計例: アコーディオン
ここまでの考え方を使って、アコーディオンを整理した形で書いてみます。
export function initAccordion() {
const accordions = document.querySelectorAll(".js-accordion");
if (accordions.length === 0) {
return;
}
accordions.forEach((accordion) => {
setupAccordion(accordion);
});
}
function setupAccordion(accordion) {
const button = accordion.querySelector(".js-accordion-button");
const panel = accordion.querySelector(".js-accordion-panel");
if (!button || !panel) {
return;
}
button.addEventListener("click", () => {
const isOpen = button.getAttribute("aria-expanded") === "true";
setAccordionState(button, panel, !isOpen);
});
}
function setAccordionState(button, panel, shouldOpen) {
button.setAttribute("aria-expanded", String(shouldOpen));
panel.hidden = !shouldOpen;
panel.classList.toggle("is-active", shouldOpen);
}関数を分けることで、役割が見えやすくなります。
initAccordion: 全体の初期化setupAccordion: 1つのアコーディオンを準備setAccordionState: 開閉状態を反映
ページごとのJavaScript
サイトによっては、共通JSとページ専用JSを分けることがあります。
assets/js/
main.js
pages/
top.js
contact.js
modules/
accordion.js
modal.jsトップページだけのファーストビュー演出や、フォームページだけの処理は、ページ専用ファイルに分けると見通しが良くなります。
ただし、ビルド環境やCMSの仕様によって管理方法は変わります。
まずは案件のルールに合わせましょう。
AstroやNext.jsとの関係
最近のWeb制作では、AstroやNext.jsのようなフレームワークを使ってサイトを作ることがあります。
これらは、HTML、CSS、JavaScriptを直接書く制作とまったく別物ではありません。
コンポーネントとしてUIを分けたり、MarkdownやCMSの内容からページを生成したり、ビルドして公開用のHTMLやJavaScriptを作ったりするための仕組みです。
| 名前 | Web制作での位置づけ |
|---|---|
| Astro | 静的サイトやコンテンツ中心のサイトと相性がよいフレームワーク |
| Next.js | Reactを使ったサイトやWebアプリでよく使われるフレームワーク |
| SSG | ビルド時にHTMLを生成する考え方 |
| SSR | アクセス時にサーバー側でHTMLを生成する考え方 |
この講座では、AstroやNext.jsの使い方までは深掘りしません。
ただし、どの環境でも、最終的にはブラウザ上のHTMLをJavaScriptで操作する場面があります。
そのため、DOM操作、イベント、初期化関数、対象要素がない時のガード処理といった基本は、フレームワークを使う前の土台になります。
よくある失敗
保守性の面でよくある失敗を整理します。
main.jsにすべてを書き続けて、どこに何があるかわからなくなる- 同じ開閉処理を複数箇所にコピペする
- 対象要素がないページでエラーになる
- 1ページに1つだけの前提で書いて、複数設置に対応できない
- CSS用クラスをJavaScriptで取得して、デザイン修正で壊れる
- 関数名が
aaaやtestのように意味を持たない - コメントが多いのに、なぜ必要なのかが書かれていない
保守しやすさは、後から一気に足すより、最初の小さなルールでかなり変わります。
実務でのチェックリスト
JavaScriptを書いた後は、次の項目を確認しましょう。
この章のまとめ
この章では、保守しやすいJavaScript設計を学びました。
- 処理が増えたら、UIごとにファイルや関数を分ける
main.jsは各初期化関数を呼び出す入口にする- 対象要素がないページでは何もしないガード処理を書く
- 複数設置に対応するには、コンポーネント内から要素を探す
- CSS用クラスとJavaScript用クラスを分ける
- 状態クラスは
is-activeなど、プロジェクト内でルールを決める - コメントは「何を」より「なぜ」を補足する
- AstroやNext.jsのような環境でも、DOM、イベント、状態管理の基礎は土台になる