やったこと
class属性で状態用のclassをJavaScriptで付けたり外したりすることで、CSSでclassの有無で見た目を切り替えるという事をよくやっています。
そこで、ある要素Xが存在する場合にある要素Yの表示を切り替えたいと思いました。今どきはJavaScriptのフレームワークでコントロールしてしまうとは思いますが(Vueのv-showなど)
調べましたが、サンプルが子要素か兄弟要素を使うモノしか見当たらず悩んで試行錯誤してました。
結果、body:has(要素Xのセレクタ) 要素Yのセレクタ
とすればできたので書き残しておきます。
デモ
次のデモでは5秒毎に#target
要素(要素X)を交互に追加・削除しており、これがDOM上にあるかどうかで、#view
要素(要素Y)のスタイルを変える、という感じになっています。chrome/firefox/safari(iOS)で確認。
Demo: https://jsbin.com/haleqeqiji/2 (コード)
/* "ある"ときも"ない"ときも適用する */
#view::before {
content: '表示されることは無い';
font-weight: bold;
}
/* #target が "ある" ときに適用する */
body:has(#target) #view::before {
content: 'あるよ';
color: green;
}
/* #target が "ない" ときに適用する */
body:not(:has(#target)) #view::before {
content: 'ないよ';
color: red;
}
(デモでは::before
という疑似要素を使っていますが、任意のセレクタで大丈夫です)
解説
なぜ機能するか?
body:has(セレクタ)
とすることで、bodyから見てセレクタを満たせるか、という感じになります。
body:not(:has(セレクタ))
は逆で、bodyから見てセレクタを満たせないか、という感じになります。
body:has(セレクタ)
もしくはbody:not(:has(セレクタ))
が満たされると、bodyが対象要素(Subject element)となります。
今回の例では、body:has(セレクタ) #view
もしくはbody:not(:has(セレクタ)) #view
としているので、bodyの子孫要素である #view
が対象要素となるので、狙った通りうまくいく、という感じです。
難点はCSSセレクタの意図がわかりにくいところでしょうか。
bodyでないといけないのか?
「有無を見たい要素(今回は:has(セレクタ)
)」と「対象要素(今回は#view
)」が同じ子(子孫)に存在するならbodyでなくても機能します。
Demo: https://jsbin.com/nayucazopi/3/ (コード)
しかしbodyなら必ず親(先祖)にいる要素なので確実な気はします。
bodyは省略できないのか?
bodyはコンテンツ上一番上にある要素なので、省略したいところですが、:has
から始めるやり方はうまく機能しません。
困ったことに、ちょっとうまく動いてしまうという事があります。
具体的には、"ある"時だけを考えたときは省略しても動いているように見えてしまう、というのがあります。
/* "ある"ときも"ない"ときも適用する */
#view::before {
content: 'ない?';
font-weight: bold;
}
/* #target が "ある" ときに適用する */
:has(#target) #view::before {
content: 'あるよ';
color: green;
}
"ある"ときだけ適用するのはそれっぽく見えます。
しかし、"ない"時に適用しようとすると、この書き方はうまくいきません。
/* "ある"ときも"ない"ときも適用する */
#view::before {
content: 'ある?';
font-weight: bold;
}
/* #target が "ない" ときに適用したかった */
:not(:has(#target)) #view::before {
content: 'ないよ';
color: red;
}
#target
の要素があるのに、:not(:has(#target))
が適用されてしまい、意図しない表示となってしまいました。難しい。
参考
(:hoverと組み合わせているサンプルはどういう考え方をすれば身に付くのだろう…)