shadow DOMを使うとカプセル化されるため、外部CSSの影響を受けないと思っていました。
しかし 継承可能なプロパティー(colorやfont等) はshadow DOMの内部にも適用されるため、思った通りの表示にならないことがあります
上記の動作に気が付いた経緯
既存の古いWebアプリの一部にReact(+TailWind CSS)を埋め込んだ際、既存のCSSの影響を避けるためにshadow DOM内にrenderする仕組みを作りました。ところが、Tailwind CSSで指定した文字色にならない(古いWebアプリのCSSが適用されていた)という現象が起きたため原因の調査を行いました
ReactをShadow DOM内にrenderする+継承可能なプロパティーをリセットするコンポーネントを作成しました
https://qiita.com/murasuke/items/50d9327332cbd2d40794
shadow DOMのカプセル化の仕様
- shadow DOM内部では、外部CSSのセレクターにマッチしない(外部CSSは適用されない)
- しかし、継承プロパティについては外部のDOMからshadow DOMへ継承される
継承されるプロパティーと継承されないプロパティーについて
- 継承される主なプロパティ(親要素を引き継いでも問題がないもの。主にテキスト関連)
- color, font, visibility, line-heightなど
- 継承されない主なプロパティー(見た目やレイアウトに関するスタイル)
- text-decoration, background, border, margin, padding, width, heightなど
※つまり、shadow DOMのコンテナ要素に何らかのスタイルが継承されている場合、shadow DOM内部にもスタイルが継承され、フォントの色、フォントサイズなどが思った通りにならない場合がある、ということでした
確認用html
子要素に継承されるプロパティ(color)と、継承されないプロパティー(border)を用意し、セレクタで適用する箇所を変更して動作確認を行います
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>shadow DOM inherit style</title>
<style>
/* この部分のセレクタを変更して動作を確認する */ {
color : #d22; /* (子要素に)継承されるスタイル */
border: medium dashed #2d2; /* 継承されないスタイル */
}
</style>
</head>
<body>
<h1>shadow DOMの外<h1></h1>
<div id="host"></div>
<script>
// shadow DOMを作り<h1>を追加
const host = document.querySelector("#host");
const shadow = host.attachShadow({ mode: "open" });
const span = document.createElement("h1");
span.textContent = "shadow DOMの中<h1>";
shadow.appendChild(span);
</script>
</body>
</html>
- DOMは以下のようになります(jsでshadow DOMを作り、その中に<h1>タグを追加している)
- 上記サンプルではセレクターが無いため、<h1>のデフォルトで表示されます(構文エラーで無視される)
影響を受けない例
- スタイルを<h1>に適用した場合、shadow DOM側はカプセル化されているため適用されません
<style>
/* h1に適用した場合、h1とその子要素にしか適用されないので、shadow DOMには影響しない */
h1 {
color : #d22; /* (子要素に)継承されるスタイル */
border: medium dashed #2d2; /* 継承されないスタイル */
}
</style>
shadow DOMが外部CSSの影響を受ける例①
body要素に適用した場合はshadow DOMも子要素になります。そのため、継承されるプロパティ(color
)はshadow DOM内部にも適用されます
<style>
/* body要素に適用した場合、継承されるスタイルはshadow DOM内にも適用される */
body {
color : #d22; /* (子要素に)継承されるスタイル */
border: medium dashed #2d2; /* 継承されないスタイル */
}
</style>
-
color
がshadow DOM内にも適用されました(border
は<body>のみに適用されています)
shadow DOMが外部CSSの影響を受ける例②
全称セレクタ(*)の場合、全てのタグに適用されます
<style>
/* 全称セレクタの場合、全てのタグに適用されるため継承しないスタイルも反映される */
* {
color : #d22; /* (子要素に)継承されるスタイル */
border: medium dashed #2d2; /* 継承されないスタイル */
}
</style>
-
color
がshadow DOM内にも適用されます
-
border
がshadow DOM内の<h1>に適用されているように見えますが、これはshadow DOMのコンテナ
に対してborderが付与されたものです
- shadow DOM内の<h1>には継承されていないので、スタイルが
淡色表示
になっています
shadow DOM側への適用を回避(取り消し)するには?
shadow DOMにCSSを追加して、プロパティー(スタイルの指定)を初期値にもどすことができます
全てのプロパティー(all
)に対し、initial
を指定することでブラウザの初期値に戻します
プロパティ | 説明 |
---|---|
initial | 仕様で決められた初期値にもどす(黒色に戻る) |
revert | ブラウザが適用する初期値に戻す(この例で適用すると、ブラウザは初期値がないため、結果としてinherit同様、親のプロパティを引き継いでしまう) |
inherit | 親要素の値を引き継ぐ |
unset | 継承するプロパティの場合は inherit、継承しないプロパティは initial と同じ |
<script>
// shadow DOMは外部のCSSの影響を受けないはずだが、(コンテナに)継承されたプロパティーは適用される
const host = document.querySelector("#host");
const shadow = host.attachShadow({ mode: "open" });
// shadow DOM側で継承を取り消す(デフォルトへ戻す)スタイルを追加する
// :hostは、CSSの擬似クラスで、shadow DOM のシャドウホスト(ルート)を選択する
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
:host {
all: initial;
}
`);
shadow.adoptedStyleSheets = [sheet];
const span = document.createElement("h1");
span.textContent = "shadow DOMの中<h1>";
shadow.appendChild(span);
</script>
shadow DOM内のスタイルが初期値に戻りました