en.javascript.info/3-webcomponents/5-css-scoping/article.md
2015-04-23 12:31:37 +03:00

304 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Стили и селекторы
Стилизация Shadow DOM покрывается более общей спецификацией ["CSS Scoping"](http://drafts.csswg.org/css-scoping/).
По умолчанию стили внутри Shadow DOM относятся только к его содержимому.
[cut]
Например:
```html
<!--+ run autorun="no-epub" -->
<p>Жили мы тихо-мирно, и тут...</p>
<p id="elem">Доброе утро, страна!</p>
<template id="tmpl">
*!*
<style>
p {
color: red;
}
</style>
*/!*
<h3><content></content></h3>
<p>Привет из подполья!</p>
</template>
<script>
var root = elem.createShadowRoot();
root.appendChild(tmpl.content.cloneNode(true));
</script>
```
При запуске окрашенным в красный цвет окажется только `<p>` внутри Shadow DOM. Обратим внимание, окрасился именно тот элемент, который находится непосредственно в Shadow DOM. А элементы, которые отображены в Shadow DOM при помощи `<content>`, этот стиль не получили -- у них есть свои, заданные на внешней странице.
## Внешний стиль для Shadow DOM
Граница между Shadow DOM и основным DOM, хоть и существует, но при помощи специальных селекторов её можно переходить.
Если нужно с основной страницы стилизовать или выбрать элементы внутри Shadow DOM, то можно использовать селекторы:
<ul>
<li>**`::shadow` -- выбирает корень Shadow DOM.**
Выбранный элемент сам по себе не создаёт CSS box, но служит отправной точкой для дальшейшей выборки уже внутри дерева Shadow DOM.
Например, `#elem::shadow > div` найдёт внутри Shadow DOM `#elem` элементы `div` первого уровня.</li>
<li>**`>>>` -- особого вида CSS-селектор для всех элементов Shadow DOM, который полностью игнорирует границы между DOM'ами, включая вложенные подэлементы, у которых тоже может быть свой Shadow DOM.**
Например, `#elem >>> span` найдёт все `span` внутри Shadow DOM `#elem`, но кроме того, если в `#elem` есть подэлементы, у которых свой Shadow DOM, то оно продолжит поиск в них.
Вот пример, когда внутри одного Shadow DOM есть `<input type="date">`, у которого тоже есть Shadow DOM:
```html
<!--+ run -->
<style>
#elem::shadow span {
/* для span только внутри Shadow DOM #elem */
border-bottom: 1px dashed blue;
}
#elem >>> * {
/* для всех элементов внутри Shadow DOM #elem и далее внутри input[type=date] */
color: red;
}
</style>
<p id="elem"></p>
<script>
var root = elem.createShadowRoot();
root.innerHTML = "<span>Текущее время:</span> <input type='date'>";
</script>
```
</li>
<li>Кроме того, на Shadow DOM действует обычное CSS-наследование, если свойство поддерживает его по умолчанию.
В этом примере CSS-стили для `body` наследуются на внутренние элементы, включая Shadow DOM:
```html
<!--+ run autorun="no-epub" -->
<style>
body {
color: red;
font-style: italic;
}
</style>
<p id="elem"></p>
<script>
elem.createShadowRoot().innerHTML = "<span>Привет, мир!</span>";
</script>
```
Внутренний элемент станет красным курсивом.
</li>
</ul>
[warn header="Нельзя получить содержимое встроенных элементов"]
Описанные CSS-селекторы можно использовать не только в CSS, но и в `querySelector`.
Исключением являются встроенные элементы типа `<input type="date">`, для которых CSS-селекторы работают, но получить их содержимое нельзя.
Например:
```html
<!--+ run -->
<p id="elem"></p>
<script>
var root = elem.createShadowRoot();
root.innerHTML = "<span>Текущее время:</span> <input type='date'>";
// выберет только span из #elem
// вообще-то, должен выбрать span и из вложенных Shadow DOM,
// но для встроенных элементов - не умеет
alert( document.querySelectorAll('#elem::shadow span').length ); // 1
</script>
```
[/warn]
## Стиль в зависимости от хозяина
Следующие селекторы позволяют изнутри Shadow DOM выбрать внешний элемент ("элемент-хозяин"):
<ul>
<li>`:host` выбирает элемент-хозяин, в котором, живёт Shadow DOM.
Хозяин :host выбирается в именно в контексте Shadow DOM.
То есть, это доступ не к внешнему элементу, а, скорее, к корню текущего Shadow DOM.
После `:host` мы можем указать селекторы и стили, которые нужно применить, если хозяин удовлетворяет тому или иному условию, например:
```html
<style>
:host > p {
color: green;
}
</style>
```
Этот селектор сработает для `<p>` первого уровня внутри Shadow DOM.
</li>
<li>`:host(селектор хозяина)` выбирает элемент-хозяин, если он подходит под селектор.
Этот селектор используется для темизации хозяина "изнутри", в зависимости от его классов и атрибутов. Он отлично добавляет просто `:host`, например:
```css
:host p {
color: green;
}
:host(.important) p {
color: red;
}
```
Здесь параграфы будут иметь `color:green`, но если у хозяина класс `.important`, то `color:red`.
</li>
<li>`:host-context(селектор хозяина)` выбирает элемент-хозяин, если какой-либо из его родителей удовлетворяет селектору, например:
```css
:host-context(h1) p {
/* селектор сработает для p, если хозяин находится внутри h1 */
}
```
Это используется для расширенной темизации, теперь уже не только в зависимости от его атрибутов, но и от того, внутри каких элементов он находится.
</li>
</ul>
Пример использования селектора `:host()` для разной расцветки Shadow DOM-сообщения, в зависимости от того, в каком оно `<p>`:
```html
<!--+ run autorun="no-epub" no-beautify -->
*!*
<p class="message info">Доброе утро, страна!</p>
*/!*
*!*
<p class="message warning">Внимание-внимание! Говорит информбюро!</p>
*/!*
<template id="tmpl">
<style>
.content {
min-height: 20px;
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
*!*
:host(.info) .content {
color: green;
}
:host(.warning) .content {
color: red;
}
*/!*
</style>
<div class="content"><content></content></div>
</template>
<script>
var elems = document.querySelectorAll('p.message');
elems[0].createShadowRoot().appendChild( tmpl.content.cloneNode(true) );
elems[1].createShadowRoot().appendChild( tmpl.content.cloneNode(true) );
</script>
```
## Стиль для content
Тег `<content>` не меняет DOM, а указывает, что где показывать. Поэтому если элемент изначально находится в элементе-хозяине -- внешний документ сохраняет к нему доступ.
К нему будут применены стили и сработают селекторы, всё как обычно.
Например, здесь применится стиль для `<span>`:
```html
<!--+ run autorun="no-epub" no-beautify -->
<style>
*!*
span { text-decoration: underline; }
*/!*
</style>
<p id="elem"><span>Доброе утро, страна!</span></p>
<template id="tmpl">
<h3><content></content></h3>
<p>Привет из подполья!</p>
</template>
<script>
elem.createShadowRoot().appendChild( tmpl.content.cloneNode(true) );
</script>
```
В примере выше заголовок "Доброе утро, страна!", который пришёл как `<span>` из внешнего документа, будет подчёркнут,
Итак, стили основного DOM-дерева применяются, всё в порядке.
Но что, если Shadow DOM тоже "имеет виды" на `<content>` и хочет стилизовать вставленное? Это тоже возможно.
**Для обращения к "содержимому" `<content>` из стилей внутри Shadow DOM используется псевдоэлемент `::content`.**
Например, изнутри Shadow DOM селектор `content[select="h1"]::content span` найдёт элемент `<content select="h1">` и его содержимом* отыщет `<span>`.
В примере ниже селектор `::content span` стилизует все `<span>` внутри всех `<content>`:
```html
<!--+ run no-beautify -->
<style>
*!*
span { text-decoration: underline; }
*/!*
</style>
<p id="elem"><span>Доброе утро, страна!</span></p>
<template id="tmpl">
<style>
*!*
::content span { color: green; }
*/!*
</style>
<h3><content></content></h3>
<span>Привет из подполья!</span>
</template>
<script>
elem.createShadowRoot().appendChild( tmpl.content.cloneNode(true) );
</script>
```
Текст внутри `<h3>` -- зелёный и подчёркнутый одновременно, но стилизуется именно тот `<span>`, который показан в `<content>`, а тот, который просто в Shadow DOM -- нет.
Приоритет селекторов расчитывается по [обычным правилам специфичности](http://www.w3.org/TR/css3-selectors/#specificity), если же приоритеты стилей на странице и в Shadow DOM и на странице равны, то, как описано в секции [Cascading](http://dev.w3.org/csswg/css-scoping/#cascading), побеждает страница, а для `!important`-стиля побеждает Shadow DOM.
## Итого
По умолчанию стили и селекторы из DOM-дерева действуют только на те элементы, в которых сами находятся.
Границу можно преодолевать, причём проще, естественно, от родителя к Shadow DOM, чем наоборот:
<ul>
<li>Снаружи можно выбирать и стилизовать элементы внутри Shadow DOM -- при помощи селекторов `::shadow` и `>>>`.</li>
<li>Изнутри Shadow DOM можно стилизовать не только то, что изначально в Shadow DOM, но и узлы, показываемые в `<content>`.</li>
<li>Также можно ставить стиль в зависимость от хозяиня при помощи селекторов `::host`, `::host-context`, но выбирать и стилизовать произвольные теги внутри хозяина нельзя.</li>
</ul>