もくじ
フォームでよく使うセレクトボックス(<select>
タグ)は便利ですが、デザインの自由度が低く、カスタマイズが難しいという悩みを持ったことはありませんか?
本記事では、JavaScriptとCSSを使ってドロップダウン風のカスタムセレクトボックスを自作する方法を紹介します。
クリックで選択肢を表示し、選んだ値をフォームに反映する仕組みを、自前で実装していきます。
実際のコードを交えて、以下のようなUIを構築します。
- クリックで選択肢を開閉
- 選択した値が見た目にも反映される
- 複数のセレクトに対応
- スクロールやリサイズ時にも表示位置を自動調整
チェックボックスのデザインをカスタマイズする方法は以下の記事で解説しています。
-
【CSS】チェックボックスのデザインをカスタマイズする方法
もくじチェックボックスのデザインをカスタマイズする方法サンプル実装コードコード解説チェック時のチェックマークの色だけを変更する(背景は白のまま)サンプル実装コードチェックボックスからチェックマークをは ...
ラジオボタンのデザインをカスタマイズする方法は以下の記事で解説しています。
-
【CSS】ラジオボタンのデザインをカスタマイズする方法
もくじラジオボタンのデザインをカスタマイズする方法サンプル実装コードコード解説ラジオボタン選択時の円の色を変更する方法サンプル実装コードラジオボタンにホバーアニメーションを実装サンプル実装コードまとめ ...
なぜカスタムセレクトを使うのか
HTMLの<select>
タグは、各ブラウザで既定のスタイルが適用され、デザインを自由に変更することが困難です。
また、選択肢の開閉アニメーションやリッチなUIには不向きです。
一方、JavaScriptで自作すれば、次のようなメリットがあります。
- 見た目を完全にカスタマイズできる
- スムーズな表示 / 非表示アニメーションを実装できる
- より多くのUIフレームワークとの親和性
ただし、アクセシビリティやキーボード操作対応には注意が必要です。
本記事ではマウス操作に限定して実装します。
【サンプル】ドロップダウン風セレクトボックス
【実装コード】ドロップダウン風セレクトボックス
HTMLの基本構造
以下は、カスタムセレクトボックスのHTML構造です。
Copyをクリックするとコピーできます。
<div class="custom-select">
<div class="custom-select-selected">選択してください</div>
<div class="custom-select-options">
<div class="custom-select-option" data-value="">選択してください</div>
<div class="custom-select-option" data-value="option1">オプション1</div>
<div class="custom-select-option" data-value="option2">オプション2</div>
</div>
<input name="name1" class="custom-select-value" type="hidden" value="">
</div>
<div class="custom-select">
<div class="custom-select-selected">選択してください</div>
<div class="custom-select-options">
<div class="custom-select-option" data-value="">選択してください</div>
<div class="custom-select-option" data-value="option1">オプション1</div>
<div class="custom-select-option" data-value="option2">オプション2</div>
<div class="custom-select-option" data-value="option3">オプション3</div>
<div class="custom-select-option" data-value="option4">オプション4</div>
<div class="custom-select-option" data-value="option5">オプション5</div>
</div>
<input name="name2" class="custom-select-value" type="hidden" value="">
</div>
.custom-select-selected
: 現在の選択状態を表示.custom-select-options
: 選択肢リスト(クリックで開閉).custom-select-option
: 各選択肢.custom-select-value
: フォームで送信される値
CSSで見た目を整える
選択エリアとドロップダウンリストのデザインは以下のように定義します。
Copyをクリックするとコピーできます。
.custom-select {
position: relative;
width: 100%;
margin: 1em 0;
}
.custom-select-selected {
padding: 0.7em 1em;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
.custom-select-selected:active {
background: transparent;
}
.custom-select-options {
display: none;
position: fixed;
width: 100%;
max-height: 10em;
overflow-x: visible;
overflow-y: auto;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.custom-select-option {
position: relative;
padding: 0.5em 1em 0.5em 2em;
cursor: pointer;
}
.custom-select-option:active {
background: transparent;
}
.custom-select-option.is-selected::before {
position: absolute;
top: 50%;
left: 1em;
width: 1em;
height: 1em;
background-image: url('data:image/svg+xml;utf8,<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 8L7 11L12 5" stroke="%23222" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>');
background-size: contain;
background-repeat: no-repeat;
transform: translate(-50%, -50%);
content: "";
}
.custom-select-option:hover {
background-color: #f0f0f0;
}
JavaScriptで機能を実装する
Copyをクリックするとコピーできます。
document.addEventListener('DOMContentLoaded', function() {
// イベント登録と開閉処理
const selectedElements = document.querySelectorAll('.custom-select-selected');
selectedElements.forEach(function(selectedEl) {
selectedEl.addEventListener('click', function(event) {
event.stopPropagation(); // 親のクリックイベントを止める
const customSelect = selectedEl.closest('.custom-select');
const optionsEl = customSelect.querySelector('.custom-select-options');
const isHidden = window.getComputedStyle(optionsEl).display === 'none';
// すべてのオプションを閉じる
document.querySelectorAll('.custom-select-options').forEach(function(el) {
hideElement(el);
});
if (isHidden) {
showElement(optionsEl);
adjustListPosition(optionsEl);
}
});
});
//選択処理と hidden input への反映
const optionElements = document.querySelectorAll('.custom-select-option');
optionElements.forEach(function(optionEl) {
optionEl.addEventListener('click', function(event) {
event.stopPropagation(); // クリック伝播を止める
const customSelect = optionEl.closest('.custom-select');
const selectedTextEl = customSelect.querySelector('.custom-select-selected');
const hiddenInput = customSelect.querySelector('.custom-select-value');
const optionsContainer = customSelect.querySelector('.custom-select-options');
const value = optionEl.dataset.value;
customSelect.querySelectorAll('.custom-select-option').forEach(function(el) {
el.classList.remove('is-selected');
});
if (value) {
optionEl.classList.add('is-selected');
}
selectedTextEl.textContent = optionEl.textContent;
if (hiddenInput) hiddenInput.value = value;
hideElement(optionsContainer);
});
});
// 外側クリックで閉じる
document.addEventListener('click', function() {
document.querySelectorAll('.custom-select-options').forEach(function(el) {
hideElement(el);
});
});
// 表示位置の自動調整(スクロール・リサイズ対応)
window.addEventListener('resize', handleAdjustAll);
window.addEventListener('scroll', handleAdjustAll);
// 関数定義
function handleAdjustAll() {
const optionLists = document.querySelectorAll('.custom-select-options');
optionLists.forEach(function(optionEl) {
if (window.getComputedStyle(optionEl).display === 'block') {
adjustListPosition(optionEl);
}
});
}
function adjustListPosition(element) {
const customSelect = element.closest('.custom-select');
const customSelectOptions = customSelect.querySelector('.custom-select-options');
const customSelectRect = customSelect.getBoundingClientRect();
const customSelectOptionsHeight = customSelectOptions.offsetHeight;
const windowHeight = window.innerHeight;
const spaceBelow = windowHeight - customSelectRect.bottom;
const left = customSelectRect.left;
const width = customSelectRect.width;
const top = spaceBelow >= customSelectOptionsHeight
? customSelectRect.bottom + 5
: customSelectRect.top - customSelectOptionsHeight - 5;
setStyles(customSelectOptions, {
position: 'fixed',
zIndex: '2',
top: top + 'px',
left: left + 'px',
width: width + 'px'
});
}
function setStyles(element, styles) {
Object.keys(styles).forEach(function(key) {
element.style[key] = styles[key];
});
}
function showElement(element) {
element.style.display = 'block';
}
function hideElement(element) {
element.style.display = 'none';
}
});
よくあるエラーと対処法
位置がずれる
CSSのposition: fixed
とgetBoundingClientRect()
の扱いを見直しましょう。
まとめ
JavaScriptを使用することで、<select>
では実現できないスタイリッシュなセレクトボックスを自作することができます。
今回の実装では、見た目の自由度や複数対応、位置調整など基本機能を網羅しています。
今後は以下のような改良も可能です。
- CSSアニメーションの追加
- キーボード操作対応(Tab, Enter, ↑↓キーなど)
- アクセシビリティ強化(
aria
属性の追加)
よくある質問(FAQ)
Q. 複数のカスタムセレクトに対応できますか?
A. はい、全ての.custom-select
に対してイベントが設定されています。
Q. フォームで送信される値は?
A. .custom-select-value
のhidden input
に選択値が反映され、通常のフォームと同様に値が送信されます。
Q. 初期状態で選択済みにしたい
A. 初期状態で選択済みにしたい場合は、HTML上の.custom-select-selected
のテキストを、初期値として選びたいオプションと一致させておく必要があります。
また、custom-select-value
にvalue
を設定し、それに対応する.custom-select-option
のclassにis-selected
を追加する必要があります。
チェックボックスのデザインをカスタマイズする方法は以下の記事で解説しています。
-
【CSS】チェックボックスのデザインをカスタマイズする方法
もくじチェックボックスのデザインをカスタマイズする方法サンプル実装コードコード解説チェック時のチェックマークの色だけを変更する(背景は白のまま)サンプル実装コードチェックボックスからチェックマークをは ...
ラジオボタンのデザインをカスタマイズする方法は以下の記事で解説しています。
-
【CSS】ラジオボタンのデザインをカスタマイズする方法
もくじラジオボタンのデザインをカスタマイズする方法サンプル実装コードコード解説ラジオボタン選択時の円の色を変更する方法サンプル実装コードラジオボタンにホバーアニメーションを実装サンプル実装コードまとめ ...