一覧WB-08-1 · CSSの適用規則

CSSセレクター

カードと選択欄を持つ同じ文書へセレクタを適用します。子孫と直接の子、兄弟、属性、位置、状態を順に調べます。docはブラウザ内の文書、querySelectorAllは一致する要素を文書順で取得するAPIです。

説明

CSSのセレクタは、スタイルを適用する要素の集合を指定します。タグ・class・idだけでなく、親子や兄弟の関係、属性、チェック状態などによっても対象が変わります。見た目の変化を追う前に、どの要素が選ばれたかを確認すると指定の誤りを見つけやすくなります。

カードと選択欄を持つ同じ文書へセレクタを適用します。子孫と直接の子、兄弟、属性、位置、状態を順に調べます。docはブラウザ内の文書、querySelectorAllは一致する要素を文書順で取得するAPIです。

1対象となる文書を作る

二枚の直接のカードと、aside内のカードを用意します。

(globalThis.doc=createFormDocumentブラウザ内に文書を開く('<!doctype html><html lang="ja"><head><style>body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}.card{padding:4px;border:1px solid #555}.hot{color:#f3cf47}</style></head><body><section id="list"><h2>資料</h2><article id="a" class="card hot" data-kind="book">HTML入門</article><article id="b" class="card" data-kind="video">CSS動画</article><aside><article id="c" class="card hot" data-kind="book-extra">補足資料</article></aside></section><div id="choices"><label class="option"><input id="html" type="checkbox" checked>HTML</label><label class="option"><input id="css" type="checkbox">CSS</label></div></body></html>'),[...doc.querySelectorAll(".card")cardクラスの要素].map(e=>e.id)一致したidを表示)

.cardはclassにcardを持つa・b・cの三要素を選びます。classは複数の語を持てるため、card hotも.cardに一致します。DOMのidを表示して、選ばれた対象を特定しています。

2同じ要素に二つのclassを要求

間に空白を入れず、二つのclass条件を連ねます。

[...doc.querySelectorAll(".card.hot"同じ要素にcardとhotの両方)].map(e=>e.id)

aとcが一致します。.card.hotは同じ要素が両方のclassを持つ条件です。.card .hotのように空白を入れると、cardの子孫にあるhotという別の関係になります。

3子孫と直接の子

空白と>で、section内のカードを選ぶ範囲を比較します。

({descendants:[...doc.querySelectorAll("#list .card"深さを問わない子孫)].map(e=>e.id),children:[...doc.querySelectorAll("#list > .card"直接の子だけ)].map(e=>e.id)})

子孫ではa・b・c、直接の子ではa・bです。cはasideを一段挟むため、#list > .cardには一致しません。階層が一段増えたときに指定が届くかどうかも、この違いで決まります。

4隣接する兄弟と後続の兄弟

aの直後と、それ以降にある同じ親の要素を取得します。

({adjacent:[...doc.querySelectorAll("#a + *"直後の兄弟)].map(e=>e.localName+":"+e.id),following:[...doc.querySelectorAll("#a ~ *"後ろにある兄弟)].map(e=>e.localName+":"+e.id)})

+は直後のarticle#bだけ、~は後続のarticle#bとasideです。aside内のcはaの兄弟ではないため、#a ~ *には入りません。

5属性の完全一致と前方一致

data-kindの値を二通りの条件で選びます。

({exact:[...doc.querySelectorAll('[data-kind="book"]値全体の一致')].map(e=>e.id),prefix:[...doc.querySelectorAll('[data-kind^="book"]文字列の先頭が一致')].map(e=>e.id)})

完全一致はaだけ、前方一致はbook-extraのcも含みます。^=は文字列の先頭であり、単語の境界を検査する指定ではありません。classの語を探す場合の~=などとは役割が異なります。

6何番目の子か、同じタグの何番目か

list直下のarticleに対し、二種類の位置条件を使います。

({secondChild:[...doc.querySelectorAll("#list > article:nth-child(2)すべての要素の子で二番目")].map(e=>e.id),secondArticle:[...doc.querySelectorAll("#list > article:nth-of-type(2)articleの中で二番目")].map(e=>e.id)})

nth-child(2)は全要素の子の中で二番目のaです。一番目にはh2があります。nth-of-type(2)はarticleの中で二番目のbです。文字ノードはこの番号には数えません。

7チェック状態で対象が変わる

現在の:checkedを取得してから、CSSの欄にもチェックを付けます。

({before:[...doc.querySelectorAll(":checked"現在チェックされている要素)].map(e=>e.id),after:(doc.getElementById("css").checked=true現在の状態を変更,[...doc.querySelectorAll(":checked")].map(e=>e.id))})

対象はhtmlだけからhtmlとcssへ増えます。:checkedは現在の選択状態に反応し、HTMLのchecked属性を文字列として探すだけの条件ではありません。

8子の状態に応じて親を選ぶ

チェックを持つlabelを:hasで選び、その親要素へ背景色を付けます。

(globalThis.rule=doc.createElement("style"),rule.textContent=親要素を選ぶスタイル".option:has(> input:checked)チェック済みの直接の子を持つlabel{background:#22434b;padding:4px}",doc.head.append(rule),[...doc.querySelectorAll(".option:has(> input:checked)")].map(e=>e.textContent))

HTMLとCSSの二つのlabelが一致し、背景色が変わります。:hasの中の>は、そのlabelを基準にした直接の子を表します。子の状態を条件に親へスタイルを付ける指定です。

9除外と選択肢の組合せ

hot以外のカードと、aまたはbを選びます。

({notHot:[...doc.querySelectorAll(".card:not(.hot)"hotを除外)].map(e=>e.id),either:[...doc.querySelectorAll(".card:is(#a,#b)"aまたはb)].map(e=>e.id)})

:notはhotを除いたb、:isはaまたはbに一致します。これらは条件をまとめる構文で、:isや:notの引数は後の詳細度の計算にも関わります。

10記号を含むidを指定する

aのidをitem:1へ変え、CSS.escapeでセレクタ用に処理します。

(doc.getElementById("a").id="item:1",globalThis.selector="#"+CSS.escape("item:1")idの記号をセレクタ用に処理,{selector,match:doc.querySelector(selector)処理したidで対象を取得.textContent})

コロンはセレクタの構文に使われるため、idの文字として扱うにはエスケープが必要です。CSS.escapeは文字列をCSS識別子として使える形にします。querySelectorへ#item:1をそのまま渡すと、意図したid指定にはなりません。

まとめ

セレクタが選んだ要素をidなどで確認し、同じ要素の条件と親子・兄弟の関係を分けます。

属性値・位置・現在状態によって対象は変化します。

状態に応じる:hasや、記号を含むidのエスケープも、対象を正確に選ぶための指定です。

操作履歴と解説を振り返れます。

ブラウザ / HTML・DOM

1
(globalThis.doc=createFormDocument('<!doctype html><html lang="ja"><head><style>body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}.card{padding:4px;border:1px solid #555}.hot{color:#f3cf47}</style></head><body><section id="list"><h2>資料</h2><article id="a" class="card hot" data-kind="book">HTML入門</article><article id="b" class="card" data-kind="video">CSS動画</article><aside><article id="c" class="card hot" data-kind="book-extra">補足資料</article></aside></section><div id="choices"><label class="option"><input id="html" type="checkbox" checked>HTML</label><label class="option"><input id="css" type="checkbox">CSS</label></div></body></html>'),[...doc.querySelectorAll(".card")].map(e=>e.id))
[
  "a",
  "b",
  "c"
]
2
[...doc.querySelectorAll(".card.hot")].map(e=>e.id)
[
  "a",
  "c"
]
3
({descendants:[...doc.querySelectorAll("#list .card")].map(e=>e.id),children:[...doc.querySelectorAll("#list > .card")].map(e=>e.id)})
{
  "descendants": [
    "a",
    "b",
    "c"
  ],
  "children": [
    "a",
    "b"
  ]
}
4
({adjacent:[...doc.querySelectorAll("#a + *")].map(e=>e.localName+":"+e.id),following:[...doc.querySelectorAll("#a ~ *")].map(e=>e.localName+":"+e.id)})
{
  "adjacent": [
    "article:b"
  ],
  "following": [
    "article:b",
    "aside:"
  ]
}
5
({exact:[...doc.querySelectorAll('[data-kind="book"]')].map(e=>e.id),prefix:[...doc.querySelectorAll('[data-kind^="book"]')].map(e=>e.id)})
{
  "exact": [
    "a"
  ],
  "prefix": [
    "a",
    "c"
  ]
}
6
({secondChild:[...doc.querySelectorAll("#list > article:nth-child(2)")].map(e=>e.id),secondArticle:[...doc.querySelectorAll("#list > article:nth-of-type(2)")].map(e=>e.id)})
{
  "secondChild": [
    "a"
  ],
  "secondArticle": [
    "b"
  ]
}
7
({before:[...doc.querySelectorAll(":checked")].map(e=>e.id),after:(doc.getElementById("css").checked=true,[...doc.querySelectorAll(":checked")].map(e=>e.id))})
{
  "before": [
    "html"
  ],
  "after": [
    "html",
    "css"
  ]
}
8
(globalThis.rule=doc.createElement("style"),rule.textContent=".option:has(> input:checked){background:#22434b;padding:4px}",doc.head.append(rule),[...doc.querySelectorAll(".option:has(> input:checked)")].map(e=>e.textContent))
[
  "HTML",
  "CSS"
]
9
({notHot:[...doc.querySelectorAll(".card:not(.hot)")].map(e=>e.id),either:[...doc.querySelectorAll(".card:is(#a,#b)")].map(e=>e.id)})
{
  "notHot": [
    "b"
  ],
  "either": [
    "a",
    "b"
  ]
}
10
(doc.getElementById("a").id="item:1",globalThis.selector="#"+CSS.escape("item:1"),{selector,match:doc.querySelector(selector).textContent})
{
  "selector": "#item\\:1",
  "match": "HTML入門"
}

演習完了