Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Slot в Web Components
Slot - это мощный механизм для создания переиспользуемых компонентов с гибкой структурой. Расскажу как его использовать.
Что такое Slot
Slot позволяет компоненту принимать содержимое от родителя и размещать его в определённых местах:
// Простой Web Component с default slot
class Card extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
.card {
border: 1px solid #ccc;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
</style>
<div class="card">
<slot></slot>
</div>
`;
}
}
customElements.define('my-card', Card);
// Использование
// HTML:
// <my-card>
// <h1>Заголовок</h1>
// <p>Содержимое карточки</p>
// </my-card>
Default Slot
Default slot принимает всё содержимое компонента:
class Button extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
button {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
</style>
<button>
<slot></slot>
</button>
`;
}
}
customElements.define('my-button', Button);
// Использование:
// <my-button>Click me</my-button>
// <my-button><strong>Bold text</strong></my-button>
Named Slots
Named slots позволяют различать разные области содержимого:
class Modal extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 1000;
max-width: 500px;
}
.header {
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
margin-bottom: 10px;
}
.body {
margin: 15px 0;
}
.footer {
border-top: 1px solid #ddd;
padding-top: 10px;
margin-top: 10px;
text-align: right;
}
</style>
<div class="modal">
<div class="header">
<slot name="header"></slot>
</div>
<div class="body">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
`;
}
}
customElements.define('my-modal', Modal);
// Использование:
// <my-modal>
// <h2 slot="header">Подтверждение</h2>
// <p>Вы уверены?</p>
// <div slot="footer">
// <button>Отмена</button>
// <button>Подтвердить</button>
// </div>
// </my-modal>
Множественные элементы в slot
class TabPanel extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
.tabs {
display: flex;
border-bottom: 2px solid #ddd;
margin-bottom: 10px;
}
.tab-buttons {
display: flex;
gap: 10px;
}
button {
background: none;
border: none;
padding: 10px 15px;
cursor: pointer;
border-bottom: 3px solid transparent;
}
button.active {
border-bottom: 3px solid #007bff;
color: #007bff;
}
.content {
display: none;
}
.content.active {
display: block;
}
</style>
<div class="tabs">
<div class="tab-buttons">
<slot name="tab-button"></slot>
</div>
</div>
<div class="tab-content">
<slot name="tab-content"></slot>
</div>
`;
}
}
customElements.define('my-tabs', TabPanel);
// Использование:
// <my-tabs>
// <button slot="tab-button">Tab 1</button>
// <button slot="tab-button">Tab 2</button>
// <button slot="tab-button">Tab 3</button>
//
// <div slot="tab-content">Content 1</div>
// <div slot="tab-content">Content 2</div>
// <div slot="tab-content">Content 3</div>
// </my-tabs>
Работа со Slot API
class TabPanel extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
.content { display: none; }
.content.active { display: block; }
</style>
<div class="tabs-header">
<slot name="header"></slot>
</div>
<div class="tabs-content">
<slot name="content"></slot>
</div>
`;
// Получить все элементы в named slot
const contentSlot = this.shadowRoot.querySelector('slot[name="content"]');
const assignedElements = contentSlot.assignedElements();
console.log('Количество tabs:', assignedElements.length);
assignedElements.forEach((el, i) => {
el.classList.add(`tab-${i}`);
});
// Слушать изменения в slot
contentSlot.addEventListener('slotchange', (event) => {
console.log('Содержимое slot изменилось');
const nodes = contentSlot.assignedNodes();
console.log('Элементы:', nodes);
});
}
}
customElements.define('my-tab-panel', TabPanel);
Слотирование с фильтрацией
class List extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
ul { list-style: none; padding: 0; }
li { padding: 8px; border-bottom: 1px solid #eee; }
</style>
<ul>
<slot></slot>
</ul>
`;
// Получить все li элементы через default slot
const slot = this.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', () => {
const items = slot.assignedElements()
.filter(el => el.tagName === 'LI');
console.log(`Всего items: ${items.length}`);
// Стилизовать через светлый DOM
items.forEach((item, i) => {
item.style.backgroundColor = i % 2 === 0 ? '#f5f5f5' : 'white';
});
});
}
}
customElements.define('my-list', List);
// Использование:
// <my-list>
// <li>Item 1</li>
// <li>Item 2</li>
// <li>Item 3</li>
// </my-list>
Fallback содержимое
class Tooltip extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const text = this.getAttribute('text') || 'Default tooltip';
this.shadowRoot.innerHTML = `
<style>
.tooltip {
position: relative;
display: inline-block;
cursor: help;
}
.tooltip-text {
visibility: hidden;
background: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -50px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
</style>
<div class="tooltip">
<slot>Hover me</slot>
<span class="tooltip-text">${text}</span>
</div>
`;
}
}
customElements.define('my-tooltip', Tooltip);
// Использование:
// <my-tooltip text="Это подсказка">?
// <my-tooltip text="Default"> (без содержимого, будет использовано fallback)
Выводы
- Default slot -
<slot></slot>принимает всё содержимое - Named slots -
<slot name="header"></slot>для конкретных областей - Slot API -
assignedElements(),assignedNodes(),slotchangeсобытие - Fallback - содержимое внутри
<slot>показывается если нет слотированного контента - Стилизация - Shadow DOM стили не влияют на слотированный контент
- Best practice - Используй named slots для структурированных компонентов