1. コンポーネントとは
Web ページ上で HTML・CSS・JavaScript をひとまとめにした再利用可能な部品を「コンポーネント」と呼ぶ。優れたコンポーネントは以下の四要素を満たす。
- カプセル化:内部実装を隠蔽し、外部からは最小限のインターフェースのみを公開
- 正確性:仕様通りに動作し、副作用がない
- 拡張性:機能追加が容易
- 再利用性:異なるプロジェクトでも流用可能
2. スライダーコンポーネントの基本形
2.1 HTML(構造)
<div id="carousel" class="carousel">
<ul>
<li class="carousel__item carousel__item--active">
<img src="img1.jpg">
</li>
<li class="carousel__item">
<img src="img2.jpg">
</li>
</ul>
</div>
2.2 CSS(表現)
.carousel { position: relative; width: 100%; overflow: hidden; }
.carousel__item {
position: absolute; top: 0; left: 0;
opacity: 0; transition: opacity 1s;
}
.carousel__item--active { opacity: 1; }
2.3 JavaScript(振る舞い)- API 編
class Carousel {
constructor(selector) {
this.root = document.querySelector(selector);
this.slides = [...this.root.querySelectorAll('.carousel__item')];
}
get activeIndex() {
return this.slides.findIndex(s => s.classList.contains('carousel__item--active'));
}
goTo(index) {
this.slides.forEach(s => s.classList.remove('carousel__item--active'));
this.slides[index]?.classList.add('carousel__item--active');
}
next() { this.goTo((this.activeIndex + 1) % this.slides.length); }
prev() { this.goTo((this.activeIndex - 1 + this.slides.length) % this.slides.length); }
}
3. インタラクション実装(イベント駆動)
矢印・インジケータなどを追加し、ユーザーの操作に対応する。
class InteractiveCarousel extends Carousel {
constructor(selector, { autoplay = 3000 } = {}) {
super(selector);
this.autoplay = autoplay;
this.buildControls();
this.bindEvents();
if (autoplay) this.start();
}
buildControls() {
this.root.insertAdjacentHTML('beforeend', `
<button class="carousel__arrow carousel__arrow--prev"></button>
<button class="carousel__arrow carousel__arrow--next"></button>
<nav class="carousel__dots">
${this.slides.map((_, i) => `<span data-index="${i}"></span>`).join('')}
</nav>
`);
}
bindEvents() {
this.root.addEventListener('click', e => {
if (e.target.matches('.carousel__arrow--next')) this.next();
if (e.target.matches('.carousel__arrow--prev')) this.prev();
if (e.target.dataset.index != null) this.goTo(+e.target.dataset.index);
this.resetTimer();
});
this.root.addEventListener('slide', e => {
const idx = e.detail.index;
this.root.querySelectorAll('.carousel__dots span')
.forEach((dot, i) => dot.classList.toggle('is-active', i === idx));
});
}
start() {
this.timer = setInterval(() => this.next(), this.autoplay);
}
stop() { clearInterval(this.timer); }
resetTimer() { this.stop(); if (this.autoplay) this.start(); }
goTo(index) {
super.goTo(index);
this.root.dispatchEvent(new CustomEvent('slide', { detail: { index } }));
}
}
4. プラグイン化による拡張
コアクラスを肥大化させず、機能をプラグインとして外付けする。
const pluginDots = carousel => {
carousel.on('slide', e => {
const idx = e.detail.index;
carousel.root.querySelectorAll('.carousel__dots span')
.forEach((dot, i) => dot.classList.toggle('is-active', i === idx));
});
};
const pluginArrow = carousel => {
carousel.root.addEventListener('click', e => {
if (e.target.matches('.carousel__arrow--next')) carousel.next();
if (e.target.matches('.carousel__arrow--prev')) carousel.prev();
});
};
class Carousel {
...
use(...plugins) { plugins.forEach(p => p(this)); }
}
const c = new Carousel('#carousel');
c.use(pluginDots, pluginArrow);
5. テンプレート化で HTML 依存を排除
JavaScript 側で HTML を生成し、画像リストだけを受け取る構成に変更。
class Carousel {
constructor(selector, { images = [], autoplay = 3000 } = {}) {
this.root = document.querySelector(selector);
this.images = images;
this.autoplay = autoplay;
this.root.innerHTML = this.render();
this.slides = [...this.root.querySelectorAll('.carousel__item')];
this.goTo(0);
if (autoplay) this.start();
}
render() {
return `
<ul>
${this.images.map(src => `<li class="carousel__item"><img src="${src}"></li>`).join('')}
</ul>`;
}
}
6. 抽象化:汎用コンポーネントフレームワーク
class Component {
constructor(selector, opts) {
this.root = document.querySelector(selector);
this.opts = opts;
this.root.innerHTML = this.render(opts.data);
}
render(data) { /* 抽象メソッド */ return ''; }
use(...plugins) { plugins.forEach(p => p(this)); }
}
class Carousel extends Component {
render(images) {
return `<ul>${images.map(src => `<li>...</li>`).join('')}</ul>`;
}
}
まとめ
- コンポーネント設計は「構造・表現・振る舞い」を明確に分離する
- カプセル化 → 正確性 → プラグイン化 → テンプレート化 → 抽象化 のステップで再利用性を高める
- 独自フレームワークを作ることで、外部ライブラリに頼らない柔軟な開発が可能になる