【JavaScript】擬似要素(::before / ::after)のスタイルをJSとCSS変数で動的に変更する方法
Webデザインの実装において、::before や ::after といった擬似要素は非常に便利ですが、JavaScriptから直接スタイルを変更しようとして「要素が取得できない!」とつまずいた経験はありませんか?
擬似要素はHTMLのDOMツリーに存在しないため、JSの querySelector 等で直接操作することができません。ON/OFF程度のスタイルの変化であれば親要素に .is-active などのクラスを付け替える方法で対応できますが、ユーザーの操作に合わせて「色やサイズを変化させたい」場合には、事前にすべてのパターンのクラスをCSSで定義しておくのは不可能です。
そこで今回は、「CSS変数(カスタムプロパティ)」を活用し、JSから擬似要素のスタイルを自由自在に、かつリアルタイムに変更する方法をデモを交えて解説します!
デモ動作
See the Pen JSとCSS変数で擬似要素のスタイルを動的に変更する by mikanbako (@mikanbako) on CodePen.
demo: JSとCSS変数で擬似要素のスタイルを動的に変更する
実装コードの紹介
【HTML】
<div class="l-wrap">
<h2>擬似要素 動的変更デモ</h2>
<p class="p-desc">スライダーを動かして吹き出しの「ツノ」の位置を、<br>カラーピッカーで背景色を変化させてみてください!</p>
<!-- コントローラー部分 -->
<div class="p-control-area">
<label class="c-label">
ツノの位置:
<input type="range" class="c-slider js-pos-slider" min="10" max="90" step="1" value="50">
<span class="js-pos-value">50%</span>
</label>
<label class="c-label">
吹き出しの色:
<input type="color" class="c-picker js-color-picker" value="#1bb4d4">
</label>
</div>
<!-- 変化させる対象のエリア -->
<div class="p-demo-box">
<!-- この吹き出しのツノ(::after)をJS経由で変化させます -->
<div class="c-balloon js-target">
CSS変数なら、ツノの位置も<br>背景色も自由自在!
</div>
</div>
</div>
【CSS】
.l-wrap {
padding: 40px 20px;
max-width: 600px;
margin: 0 auto;
color: #3e3e3e;
text-align: center;
}
h2 {
font-size: 16px;
color: #1bb4d4;
margin-bottom: 16px;
}
.p-desc {
font-size: 16px;
font-weight: bold;
color: #666;
line-height: 1.6;
margin-bottom: 40px;
}
/* コントローラーの装飾 */
.p-control-area {
background: #fff;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
margin-bottom: 40px;
display: flex;
justify-content: center;
gap: 30px;
}
.c-label {
font-size: 16px;
font-weight: bold;
display: flex;
align-items: center;
gap: 8px;
}
.c-slider {
background-color: #f7f7f7;
height: 6px;
border-radius: 6px;
}
.c-picker {
width: 40px;
height: 24px;
}
/* デモエリア */
.p-demo-box {
padding: 40px 0;
display: flex;
justify-content: center;
}
/* =========================================
ターゲット要素(吹き出し)と擬似要素
========================================= */
.c-balloon {
position: relative;
padding: 24px 40px;
font-size: 18px;
font-weight: bold;
color: #fff;
border-radius: 12px;
text-align: center;
line-height: 1.6;
/* 💡ポイント1: CSS変数の初期値を定義しておく */
--balloon-color: #1bb4d4;
--tail-pos: 50%; /* ツノの横位置(パーセント) */
/* 本体にCSS変数の色を適用 */
background-color: var(--balloon-color);
}
/* 擬似要素(::after)で作るツノのスタイリング */
.c-balloon::after {
content: "";
position: absolute;
bottom: -15px; /* 箱の下にはみ出させる */
/* 💡ポイント2: JSで変更されるCSS変数を、leftプロパティに適用! */
left: var(--tail-pos);
/* 下向きの三角形を作る */
width: 20px;
height: 16px;
clip-path: polygon(0% 0%, 100% 0%, 50% 100%);
background-color: var(--balloon-color); /* 三角形にCSS変数の色を適用 */
transform: translateX(-50%);
}
【JavaScript】
// DOM要素の取得
const target = document.querySelector('.js-target');
const posSlider = document.querySelector('.js-pos-slider');
const posValue = document.querySelector('.js-pos-value');
const colorPicker = document.querySelector('.js-color-picker');
// 💡ポイント3: スライダーの値が変わるたびに実行
posSlider.addEventListener('input', (e) => {
const currentValue = e.target.value;
// ターゲット要素のCSS変数 '--tail-pos' に「%」をつけて上書きする
target.style.setProperty('--tail-pos', `${currentValue}%`);
// 横のテキストも更新
posValue.textContent = `${currentValue}%`;
});
// 💡ポイント4: カラーピッカーの値が変わるたびに実行
colorPicker.addEventListener('input', (e) => {
// ターゲット要素のCSS変数 '--balloon-color' を上書きする
// ※本体と、ツノ(擬似要素)の背景色が同時に変わります
target.style.setProperty('--balloon-color', e.target.value);
});
コードの解説・仕組み
なぜ擬似要素はJSで直接操作できないのか?
::before や ::after は、CSSの記述によってブラウザのレンダリング時に作り出される「仮想的な要素」です。HTMLの構造(DOM)の中には実在しないため、JavaScriptの document.querySelector などで取得して style を書き換えることができません。
CSS変数(カスタムプロパティ)を「橋渡し」に使う
.c-balloon {
...
/* 💡ポイント1: CSS変数の初期値を定義しておく */
--balloon-color: #1bb4d4;
--tail-pos: 50%; /* ツノの横位置(パーセント) */
/* 本体にCSS変数の色を適用 */
background-color: var(--balloon-color);
}
/* 擬似要素(::after)で作るツノのスタイリング */
.c-balloon::after {
...
/* 💡ポイント2: JSで変更されるCSS変数を、leftプロパティに適用! */
left: var(--tail-pos);
/* 下向きの三角形を作る */
...
background-color: var(--balloon-color); /* 三角形にCSS変数の色を適用 */
...
}
JavaScriptから直接アクセスできる「親要素(今回の場合は .c-balloon 本体)」に CSS変数(--balloon-color など) を持たせます。 擬似要素側では var(--balloon-color) としてその変数を読み込むようにCSSを書いておきます。
JSの setProperty でCSS変数の値を上書きする
// 💡ポイント3: スライダーの値が変わるたびに実行
posSlider.addEventListener('input', (e) => {
const currentValue = e.target.value;
// ターゲット要素のCSS変数 '--tail-pos' に「%」をつけて上書きする
target.style.setProperty('--tail-pos', `${currentValue}%`);
// 横のテキストも更新
posValue.textContent = `${currentValue}%`;
});
// 💡ポイント4: カラーピッカーの値が変わるたびに実行
colorPicker.addEventListener('input', (e) => {
// ターゲット要素のCSS変数 '--balloon-color' を上書きする
target.style.setProperty('--balloon-color', e.target.value);
});
JavaScript側で入力要素(input イベント)の動きを監視し、target.style.setProperty('CSS変数名', 新しい値) を使って親要素のCSS変数の値を動的に上書きします。 親要素の変数が更新されると、それを参照している擬似要素のスタイルも即座に再計算され、スムーズなスタイル変更が実現します!
まとめ
今回は、CSS変数とJavaScriptを組み合わせて、擬似要素のスタイルをリアルタイムに変更するテクニックをご紹介しました。
数パターンの決まったスタイルであれば親要素へのクラスの付け替えでも対応できますが、今回のように「ユーザーの入力値」や「マウスの座標」「スクロール量」といった無限に変化する動的な値を擬似要素に渡したい場合、このCSS変数を使ったアプローチが最適解となります。
CSSとJSのそれぞれの強みを活かした、保守性も高くスマートな実装方法なので、ぜひご自身のコーディングにも取り入れてみてください!