Предел выбора слота в веб-компоненте
Слот хорош для создания многократно используемого веб-компонента, однако у него пока есть ограничение. то, с чем я столкнулся, является проблемой стиля. вы просто не можете определить стиль внутри компонента, даже вы знаете, какова будет структура внедренного контента.
Подробности из моего поста в github здесь
https://github.com/w3c/webcomponents/issues/745
я пишу компонент и пытаюсь внедрить контент через слот извне. и попробуйте добавить стиль к определенному содержимому в shadowroot компонента. демо здесь
HTML-файл
<my-navbar>
<ul>
<li>link1</li>
<li>link2</li>
<li>link3</li>
</ul>
</my-navbar>
JS файл
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
/*worked*/
::slotted(ul)
{
color:green;
}
/*
suppose i know the outside content is "ul li",and i direct define the
style after they injected into component's slot.however, it just not
work,cause slotted selector is just a compound selector, can only
effect the first layer 'ul', can not effect the child dom 'li'*/
::slotted(ul li)
{
color:red;
}
</style>
<slot></slot>
`;
}
});
однако, он просто не работает напрямую, потому что вы просто не можете использовать сложный селектор для причины ::slot(simple_selector) здесь
затем я нашел косвенное решение - повторно добавить внешний контент в слоты внутри теневого корня компонента. демо здесь
HTML-файл
<my-navbar>
<!--a dom defined a slot property-->
<ul slot='t'>
<li>link1</li>
<li>link2</li>
<li>link3</li>
</ul>
<!--a dom not define slot property-->
<span>1234</span>
</my-navbar>
JS файл
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
ul li
{
color:red;
}
</style>
<slot name='t'></slot>
<slot ></slot>
`;
//do something later...
setTimeout(this.appendOutsideSlotContentIntoInsideSlot.bind(this), 1000)
}
appendOutsideSlotContentIntoInsideSlot()
{
//insert outside dom element which has define slot property into the specify slot inside the shadow root
debugger;
for (let objIndex=0;objIndex<this.children.length;)
{
var obj=this.children[objIndex];
if(obj.slot){
var slot=this.shadowRoot.querySelector('slot[name='+obj.slot+']');
if(slot)
{
slot.appendChild(obj);
continue;
}
}
objIndex++;
}
//insert the rest dom which has not define slot property values into the anonymous slot
var defaultSlot=Array.prototype.slice.call(this.shadowRoot.querySelectorAll('slot')).filter(function(el){ return !el.name})[0];
debugger;
if(defaultSlot)
{
while (this.children.length>0)
{
defaultSlot.appendChild(this.children[0])
}
}
}
});
ну, это работа с контентом, который определил свойство slot, но не работает снова с контентом, у которого нет свойства slot
1 ответ
За исключением нескольких наследуемых правил, содержимое слота не должно напрямую зависеть от теневого CSS вашего компонента. Они разработаны, чтобы позволить CSS вне вашего компонента быть под контролем.
Это по замыслу.
Это похоже на защиту элементов теневого DOM, на которую не влияет внешний CSS.
Прочтите раздел Стилизация распределенных узлов, который находится здесь: https://developers.google.com/web/fundamentals/web-components/shadowdom
Вы можете изменять правила CSS только для элементов верхнего уровня внутри слота. И вы даже ограничены тем, что вы можете сделать с этим. Все дочерние элементы контролируются CSS за пределами теневого DOM.
В приведенном ниже примере вы увидите, что мы можем изменить цвет и цвет фона элементов верхнего уровня или <ul>
теги:
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
::slotted(ul)
{
color: blue;
}
::slotted(.bold) {
font-weight: bold;
background-color: #222;
color: #FFF;
}
::slotted(.italic) {
font-style: italic;
background-color: #AAA;
color: #000;
}
::slotted(*)
{
color: red;
}
</style>
<slot></slot>
`;
}
});
<my-navbar>
<ul class="bold">
<li>link1</li>
<li class="italic">link2</li>
<li>link3</li>
</ul>
<ul class="italic">
<li>link1</li>
<li class="bold">link2</li>
<li>link3</li>
</ul>
</my-navbar>
В приведенном выше примере единственная причина, по которой текст красный, а не синий, состоит в том, что ::slotted(*)
влияет только на два <ul>
, имеет ту же специфику, что и ::slotted(ul)
и помещается после ::slotted(ul)
, Цвет наследуется <li>
теги, потому что так работает CSS.
Цвета фона влияют только на <ul>
теги на основе их классов, а не <li>
теги с одинаковыми классами.
В приведенном ниже примере <li>
color и background-color контролируются CSS вне теневого DOM. Внешние правила действуют так, как если бы они были более конкретными, чем теневые DOM-правила, хотя теневые DOM-правила включали tag
и class
селектор (ul.bold
).
Опять же, это по замыслу.
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
::slotted(ul)
{
color: blue;
}
::slotted(ul.bold) {
font-weight: bold;
background-color: #222;
color: #FFF;
}
::slotted(ul.italic) {
font-style: italic;
background-color: #AAA;
color: #000;
}
::slotted(*)
{
color: red;
}
</style>
<slot></slot>
`;
}
});
li {
color: #555;
backgroung-color: #ddd;
}
.bold {
font-weight: bold;
background-color: #FF0;
}
.italic {
font-style: italic;
background-color: #0FF;
}
<my-navbar>
<ul class="bold">
<li>link1</li>
<li class="italic">link2</li>
<li>link3</li>
</ul>
<ul class="italic">
<li>link1</li>
<li class="bold">link2</li>
<li>link3</li>
</ul>
</my-navbar>
Вы заметите, что цвета фона <ul>
а также <li>
теги устанавливаются на основе внешних классов bold
а также italic
,
Если вы хотите использовать <slot>
вы соглашаетесь с тем, что разработчик, использующий ваш компонент, обладает возможностью переопределения всего, что помещено в слот.
Если вы не хотите, чтобы пользователь имел такой контроль, то единственный способ предотвратить это - переместить дочерние элементы компонента в теневую DOM компонента.
Но будьте осторожны, когда вы делаете это.
Согласно правилам конструкторов веб-компонентов, вы не можете получить доступ или изменить дочерние компоненты компонента, находясь в конструкторе.
Но вы должны помнить, что connectedCallback
вызывается каждый раз, когда компонент вставляется в DOM. Так что, если разработчик удаляет, а затем повторно добавляет ваш компонент, то connectedCallback
будет вызван снова. Таким образом, вы должны добавить ворота, чтобы они не вызывались дважды.
Также вы можете добавить MutationObserver
чтобы увидеть, когда пользователь меняет дочерние элементы ваших компонентов.