<video> 要素の再生状態に応じて、動画プレイヤーの見た目を変えたい場合があります。例えば YouTube では再生中かどうかで再生ボタンの表示を切り替えたり、ミュート状態では音声のアイコンを変えたりしています。従来は play、pause、volumechange などのイベントを監視し、CSS から参照するクラスを JavaScript で付け替える方法が使われてきました。
const video = document.querySelector("video");
video.addEventListener("play", () => {
video.classList.add("is-playing");
video.classList.remove("is-paused");
});
video.addEventListener("pause", () => {
video.classList.add("is-paused");
video.classList.remove("is-playing");
});
video.addEventListener("volumechange", () => {
video.classList.toggle("is-muted", video.muted);
});この方法は確かに動作しますが、状態の同期を自前で管理する必要があり、コードが複雑になります。さらにバッファリングやシークなどの状態を扱う場合は、より多くのイベントとクラスを管理する必要があります。
CSS Selectors Level 4 で定義されるメディア状態擬似クラスを使うと、ブラウザが管理する状態へ CSS から直接アクセスできます。これにより、JavaScript でイベントを監視してクラスを付け替える必要がなくなり、状態に応じたスタイルを簡潔に記述できます。
video:playing {
outline-color: green;
}
video:paused {
outline-color: gray;
}
video:muted {
opacity: 0.8;
}この記事では 7 つのメディア状態擬似クラスの条件を確認し、:has() と組み合わせて動画プレイヤーの表示を切り替える例を紹介します。
メディア状態擬似クラスとは
メディア状態擬似クラスは、再生可能な要素の現在の状態に一致する擬似クラスです。HTML では主に <video> と <audio> が対象です。
仕様では、再生、読み込み、音声という 3 種類に分けて 7 つの擬似クラスが定義されています。
| 分類 | 擬似クラス | 一致する状態 |
|---|---|---|
| 再生状態 | :playing |
再生する意図があり、一時的に止まっていても原因が解消すれば自動で再開する状態 |
| 再生状態 | :paused |
明示的な一時停止、初回再生前、再生終了後など、:playing ではない状態 |
| 再生状態 | :seeking |
再生位置を変更している状態。:playing / :paused とは独立している |
| 読み込み状態 | :buffering |
:playing でありながら、再生を続けるデータが足りず取得を待っている状態 |
| 読み込み状態 | :stalled |
:buffering に一致し、かつ一定時間データを受信できていない状態 |
| 音声状態 | :muted |
音声が強制的に無音となる状態 |
| 音声状態 | :volume-locked |
ブラウザやユーザーにより、ページから音量を変更できない状態 |
これらの擬似クラスは、独自メディアコントロールの外観をスクリプトなしで状態に追従させる目的で追加されました。CSSWG の最初の提案では、ミュート、読み込みの停滞、シークを表す独自コントロールのために、ブラウザ判定やスクリプトのロジックが必要なことが課題として挙げられています。
:volume-locked は別の用途から提案されました。環境によっては HTMLMediaElement.volume を変更しても、実際にユーザーが聞く音量に反映されません。この状態がわかれば、操作しても効果のない音量コントロールを隠すといった UI の改善が可能です。
:playing と読み込み状態は同時に一致する
7 つの擬似クラスは、互いに排他的な状態を表す列挙型ではありません。特に注意したいのが :playing、:buffering、:stalled の関係です。
Selectors Level 4 では、バッファリングや読み込みの停滞により一時的に映像が止まっていても、原因が解消したときに自動で再開するなら
:playing に一致すると定義されています。そのため、次の組み合わせが同時に一致します。
/* 通常の再生中に一致する */
video:playing {
outline-color: green;
}
/* バッファリング中は :playing と :buffering の両方に一致する */
video:buffering {
outline-color: orange;
}
/* 読み込みの停滞中は :playing と :buffering と :stalled のすべてに一致する。
:stalled は :buffering のサブセットなので、必ず :buffering より後ろに書く */
video:stalled {
outline-color: red;
}映像が画面上で動いているかではなく、ユーザーやページが再生を継続する意図を持っているかを :playing と :paused が表していると考えると理解しやすいでしょう。HTML Standard では、
HTMLMediaElement.paused が false なら :playing、true なら :paused に一致すると具体的に定義されています。
:buffering と :stalled の違いは、データの取得状況です。:stalled ⊆ :buffering ⊆ :playing という入れ子の関係になっています。video:buffering は、再生を続けるためのデータが足りず、取得を待っている状態に一致します。:stalled は、:buffering に一致し、かつ一定時間データを受信できていない状態に一致します。
:buffering と :stalled は、:playing に一致する状態の中で、より範囲の狭い条件です。CSS の詳細度は同じなので、:stalled を先に書くと、:buffering のスタイルに上書きされてしまいます。範囲の狭いほうを後ろに置いてください。
:stalled 擬似クラスと stalled イベントは条件が異なります。擬似クラスは、再生に必要なデータが足りず、読み込みも停滞しているプレイヤー UI を表すためのものです。一方、stalled イベントは再生状態にかかわらず、ネットワークからのデータ受信が停滞したときに発火します。この違いは WHATWG Issue #12145 を受けて仕様へ明記されました。
:muted と :volume-locked の違い
:muted は、単に volume が 0 であることを表す擬似クラスではありません。HTML Standard の muted の定義に従い、
muted 属性や HTMLMediaElement.muted が有効な場合など、メディア要素が強制的に無音となる状態に一致します。
<video src="movie.mp4" muted></video>video.muted = true;これらの場合は video:muted に一致します。一方、次のように再生音量を最小にしただけでは、muted 状態ではないため :muted に一致しません。
video.volume = 0;:volume-locked は、音量がゼロかどうかではなく、ページから設定した再生音量が有効かを表します。HTML Standard では volume locked の値は実装依存です。例えばユーザーや OS が音量を上書きしており、ページから video.volume を変更しても実際の音量に反映されない環境で一致します。
具体例は iPhone 上の Safari です。Apple の「Safari HTML5 Audio and Video Guide」では、iOS の音量はユーザーの物理操作で管理され、JavaScript から
volume を設定できないと説明されています。現在の WebKit の実装でも、小画面の iOS 端末ではメディア要素の
volume locked を初期状態で有効にしています。currentUserInterfaceIdiomIsSmallScreen() で判定しているため、iPad は対象外です。つまり、iPhone ではページ内に音量スライダーを実装しても、その値で端末の出力音量を調整できません。
この擬似クラスを使うと、iPhone では効果のない音量コントロールを隠し、端末の音量ボタンを使うよう案内できます。ただし、volume locked をどの環境で有効にするかはブラウザの実装に委ねられているため、開発者が JavaScript から任意にこの状態を設定することはできません。
video 要素の状態に応じてスタイルを変える
まずは、擬似クラスが一致する <video> 要素自身をスタイルしてみましょう。次の CSS は一時停止中、再生中、データ待ちで枠線の色を変更します。
video {
outline: 4px solid transparent;
outline-offset: 4px;
}
video:paused {
outline-color: gray;
}
video:playing {
outline-color: green;
}
video:seeking {
outline-color: blue;
}
video:buffering {
outline-color: orange;
}
video:stalled {
outline-color: red;
}メディア状態擬似クラスが直接一致するのは <video> 要素です。しかし、実際には動画そのものの見た目ではなく、周囲の再生ボタンや音声コントロールの表示を切り替えたい場合が多いでしょう。再生ボタンなどは <video> の子要素にできないため、周囲の要素を変更するには :has() を組み合わせます。
以下の HTML では、動画と状態ラベル、2 つの操作ボタンを .player で囲んでいます。シーク中の表示を確認できるよう、<video> には controls を付けてブラウザ標準のシークバーを出しています。
<div class="player">
<video src="movie.mp4" preload="auto" controls></video>
<div class="states">
<span class="when-paused">一時停止中</span>
<span class="when-playing">再生中</span>
<span class="when-seeking">シーク中</span>
<span class="when-muted">ミュート中</span>
</div>
<button id="play-toggle" type="button">
<span class="when-paused">再生</span>
<span class="when-playing">一時停止</span>
</button>
<button id="mute-toggle" type="button">
<span class="when-audible">ミュート</span>
<span class="when-muted">ミュート解除</span>
</button>
</div>これらの要素は初期状態ですべて非表示にしておき、いま一致している状態のものだけを表示します。
/* 状態に対応する要素は、まずすべて非表示にしておく */
.when-paused,
.when-playing,
.when-seeking,
.when-audible,
.when-muted {
display: none;
}
/* .player の子孫のうち、いま一致している状態のものだけを表示する */
.player:has(video:paused) .when-paused,
.player:has(video:playing) .when-playing,
.player:has(video:seeking) .when-seeking,
.player:has(video:not(:muted)) .when-audible,
.player:has(video:muted) .when-muted {
display: inline;
}実際に試してみると、動画の状態に応じて確かに表示が切り替わることが確認できます。
まとめ
- メディア状態擬似クラスを使うと、
<video>や<audio>の状態を CSS から直接選択できる :playing、:paused、:seeking、:buffering、:stalled、:muted、:volume-lockedの 7 つが定義されている:stalled⊆:buffering⊆:playingという入れ子の関係にあり、7 つの状態は互いに排他的ではない:seekingは再生状態とは別の軸で、:playingとも:pausedとも同時に一致する:mutedは強制的な消音状態に一致し、単にvolume = 0を設定した状態とは異なる:volume-lockedはページ内の音量調整が機能しない状態に一致し、具体例として iPhone 上の Safari がある:has()を組み合わせると、動画の状態に応じて周囲の状態ラベルや操作ボタンを変更できる
参考
Selectors Level 4 - Resource State Pseudo-classes
HTML Standard - Pseudo-classes
HTML Standard - Media elements
CSSWG Issue #3821: additional resource state pseudo-classes for media elements
CSSWG Issue #3933: effective media volume is mutable pseudo-class for media elements
Safari HTML5 Audio and Video Guide - Volume Control in JavaScript
WebKit - HTMLMediaElement.cpp
defaultVolumeLocked()Chrome 152 Beta




