以前、こちらの記事を書きました。
しかしこの方法では、JS側でライト/ダークモードの切り替えを検知できません。
(正確には、「検知できなくもないけど、毎秒確認することになりそう 」)
そこで、本記事ではそれを検知する方法を書いていきます。
使う機能
JSの Intersection Observer API
と、前記事で紹介したCSSの prefers-color-scheme
を用います。
前者は簡単に言えば、監視したい要素が別の要素に交差するときに発火する関数を指定できます。
「別の要素」を指定しないか、null
と指定すれば、ビューポートに出入りしているのかを判定できるようです。
書いていると長くなりそうなので、詳しくは以下MDNのドキュメントをご覧ください。
後者は、ユーザーがシステムに指定している「ライトモード」「ダークモード」の値を取得することができる CSS のメディア特性です。
値は light
か dark
で、IE以外の主要なブラウザは対応しています。
実装する
コード
最終的に実装したコードは以下の通りです。
<!-- <body>の直後など、ページのどこかに記述 -->
<div id="darkmode-dummy"></div>
/* ライトモード */
@media (prefers-color-scheme: light) {
#darkmode-dummy {
display: none;
}
}
/* ダークモード */
@media (prefers-color-scheme: dark) {
#darkmode-dummy {
position: fixed;
top: 0;
left: 0;
height: 0px;
width: 0px;
display: block;
}
}
let isDarkmode = false // (or true)
// 監視対象のDOM
const darkmodeDOM = document.getElementById('darkmode-dummy') // -> <div id="darkmode-dummy"></div>
// オブザーバーを作る
const observer = new IntersectionObserver(() => {
// 変更があったときに発火される関数
isDarkmode = (getComputedStyle(darkmodeDOM).display === 'block')
// isDarkmode = !isDarkmode でも良いです(「表示」「非表示」の二値しか扱っていないので)
// もちろん、isDarkmode = window.matchMedia('(prefers-color-scheme: dark)').matches と直接取得してもokです
})
// darkmodeDOMの監視開始
observer.observe(darkmodeDOM)
何をしているのか
-
<div id="darkmode-dummy">
というダミー要素を作る - そのダミー要素は、「ライトモードであれば表示せず、ダークモードでは(目に見えないが)ビューポート左上に固定する」ようにする
- Intersection Observer APIで、ダミー要素を監視
オブザーバーには何もオプションを指定していないため、ビューポートにダミー要素が出入りすると発火されるようになります。
そのため、ダミー要素を常にビューポート内に固定することで、表示・非表示で発火するようにしています。
発火された関数内で、「ダークモード」 = 「ダミー要素が表示されているかどうか」 を判定し、その後の処理につなげます。
このようにすることで、JS側でダミー要素の表示・非表示を監視
= ライトモード(非表示)・ダークモード(表示)の監視
ができました