この記事では、DOMの追加・削除やクラス切り替えと、イベント伝播・デリゲーションなどの考え方を組み合わせて、 代表的なUIパターンをコンパクトに実装した例を紹介します。
解説:
クリック(とキーボード操作)で表示領域を切り替える、定番UIの実装です。
イベントデリゲーションでコード量を最小化しつつ、アクセシビリティ(role/aria)にも配慮します。
タブはボタン、パネルはセクション要素として分離し、タブとパネルを data属性と id で結び付けます。
<!-- タブ群 -->
<div class="tabs" id="tabsDemo">
<div class="tab-list" role="tablist" aria-label="サンプルのタブ">
<button class="tab is-active" role="tab" aria-selected="true" tabindex="0"
data-tab="a" aria-controls="panel-a">タブA</button>
<button class="tab" role="tab" aria-selected="false" tabindex="-1"
data-tab="b" aria-controls="panel-b">タブB</button>
<button class="tab" role="tab" aria-selected="false" tabindex="-1"
data-tab="c" aria-controls="panel-c">タブC</button>
</div>
<!-- パネル群 -->
<div class="tab-panels">
<section class="panel is-active" role="tabpanel" id="panel-a" aria-labelledby="tab-a">
内容A:ここに任意のコンテンツ
</section>
<section class="panel" role="tabpanel" id="panel-b" aria-labelledby="tab-b" hidden>
内容B:ここに任意のコンテンツ
</section>
<section class="panel" role="tabpanel" id="panel-c" aria-labelledby="tab-c" hidden>
内容C:ここに任意のコンテンツ
</section>
</div>
</div>表示切替は、.is-active クラスと hidden 属性を使い分けます(CSSは後述の最小例を参照)。
クリックと矢印キーでタブを切り替えます。イベントは親要素ひとつにまとめるのがポイントです。
// 親にまとめてイベントを付与(デリゲーション)
const root = document.getElementById('tabsDemo');
const tabSelector = '.tab-list .tab';
const panelSelector = '.tab-panels .panel';
function activateTab(btn) {
if (!btn) return;
const name = btn.dataset.tab;
const list = root.querySelector('.tab-list');
const tabs = root.querySelectorAll(tabSelector);
const panels = root.querySelectorAll(panelSelector);
// タブの見た目とフォーカス管理
tabs.forEach(t => {
const isActive = t === btn;
t.classList.toggle('is-active', isActive);
t.setAttribute('aria-selected', String(isActive));
t.setAttribute('tabindex', isActive ? '0' : '-1');
});
// パネルの表示切替
panels.forEach(p => {
const show = p.id === btn.getAttribute('aria-controls');
p.toggleAttribute('hidden', !show);
p.classList.toggle('is-active', show);
});
// キーボード操作時のフォーカス移動
btn.focus();
}
// クリックで切替
root.addEventListener('click', (e) => {
const btn = e.target.closest('.tab');
if (!btn || !root.contains(btn)) return;
activateTab(btn);
});
// キーボード操作(左右キーで移動、Home/End対応)
root.addEventListener('keydown', (e) => {
const current = root.querySelector(`${tabSelector}[aria-selected="true"]`);
if (!current) return;
const tabs = [...root.querySelectorAll(tabSelector)];
const idx = tabs.indexOf(current);
let nextIdx = idx;
if (e.key === 'ArrowRight') nextIdx = (idx + 1) % tabs.length;
if (e.key === 'ArrowLeft') nextIdx = (idx - 1 + tabs.length) % tabs.length;
if (e.key === 'Home') nextIdx = 0;
if (e.key === 'End') nextIdx = tabs.length - 1;
if (nextIdx !== idx) {
e.preventDefault();
activateTab(tabs[nextIdx]);
}
});必須ではありませんが、表示のオン・オフに関わる最小CSSを載せておきます。既存のCSSと競合しないよう、必要に応じて調整してください。
/* タブの見た目(必要に応じて調整) */
.tab-list { display: flex; gap: .5rem; }
.tab { padding: .4rem .8rem; border: 1px solid #ccc; background: #f7f7f7; }
.tab.is-active { background: #e6f2ff; border-color: #87bfff; }
/* パネル切り替え */
.panel[hidden] { display: none !important; }
.panel.is-active { animation: fadeIn .15s ease; }
@keyframes fadeIn {
from { opacity: 0 } to { opacity: 1 }
}下のデモで、タブをクリックまたは矢印キーで切り替えて挙動を確認してください。
解説:
ページをスクロールしたときに要素が表示範囲に入ったら、ふわっと表示させる演出です。
IntersectionObserver を使うと、スクロール位置をイベントで逐一監視するのではなく、
ブラウザが効率よく検知してくれるため、パフォーマンスに優れた実装が可能です。
IntersectionObserver は、指定要素がビューポート内に入った/出たタイミングでコールバックを呼びます。
ここでは、要素が 20% 以上見えたらクラスを付与してフェードインさせる例を示します。
const targets = document.querySelectorAll('.fadein-target');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target); // 一度表示したら監視解除
}
});
}, { threshold: 0.2 });
targets.forEach(el => observer.observe(el));
下に並んだボックスは、スクロールして画面内に入るとフェードインします。
※画面を縮小して縦スクロールを発生させると動きが確認できます。
解説:
ボタン操作でリスト項目を追加・削除できるUIです。
DOM操作(要素の生成・削除)とイベントデリゲーション(親要素にまとめてイベントを付与)を組み合わせることで、シンプルかつ拡張性のある実装が可能です。
追加時は createElement で li 要素を作り、appendChild でリスト末尾に追加します。
削除は各 li の「削除ボタン」をクリックしたときに remove() を呼びます。
イベントデリゲーションを使うことで、追加された要素にも自動的にイベントが適用されます。
const list = document.getElementById('myList');
const addBtn = document.getElementById('addBtn');
addBtn.addEventListener('click', () => {
const li = document.createElement('li');
li.innerHTML = `新しい項目 `;
list.appendChild(li);
});
list.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
e.target.closest('li').remove();
}
});
下のデモで「項目を追加」ボタンをクリックすると、新しい項目が追加されます。
各項目の「削除」ボタンを押すと、その項目が消えます。