Тег slot в HTML: слоты, запасное содержимое и slotchange

Тег slot (от английского slot, «место», «гнездо») задаёт место в готовом компоненте, куда браузер подставляет ваш текст или другие элементы. Например, рамку и оформление карточки делает сам компонент, а содержимое карточки записываете вы. Разметка компонента хранится в теневом DOM, поэтому слот записывают именно там.

Если для слота нет подходящего содержимого, браузер показывает запасное содержимое, записанное внутри самого тега slot. Вне теневого DOM слот ничего не делает: страница просто показывает его запасное содержимое.

Слот обычно используют вместе с шаблоном теневого DOM. О шаблонах читайте в статье про тег template. В статье разобраны правила распределения содержимого, запасное содержимое, ловушка с пробелами, стилизация через ::slotted, методы JavaScript для работы со слотами, событие slotchange, вложенные слоты и ручное распределение. Другие теги группы собраны в шпаргалке по тегам.

HTML
<user-card>
  <template shadowrootmode="open">
    <h3><slot name="title">Без названия</slot></h3>
    <slot>Описание не добавлено</slot>
  </template>
  <span slot="title">Анна</span><p>Дизайнер</p>
</user-card>
JavaScript
class UserCard extends HTMLElement {
  constructor() {
    super();
    const root = this.attachShadow({ mode: "open" });
    root.innerHTML = `
      <h3><slot name="title">Без названия</slot></h3>
      <slot>Описание не добавлено</slot>`;
  }
}
customElements.define("user-card", UserCard);

Тег slot в HTML: что это и где он работает

Сначала о том, какие части нужны слоту и что он делает без них.

Слоту нужны два участника: компонент и элементы, которые в него вставляют. Компонентом здесь называют обычный элемент страницы со спрятанной внутренней разметкой, она называется теневым DOM. Вставляемые элементы: обычные дочерние элементы компонента, записанные между его тегами. Теневой DOM создаёт скрипт методом attachShadow() или атрибут shadowrootmode у template прямо в разметке. Браузер выводит внутреннюю разметку вместо дочерних элементов компонента, а слоты показывают, в каком месте внутренней разметки окажутся эти дети. Сами дети остаются в документе: их находит document.querySelector, и на них действуют стили страницы.

Слот без теневого DOM ничего не распределяет. Если записать <slot> в обычную разметку, у него будет display: contents, нулевые размеры и пустой assignedNodes(), а на странице выведется его запасное содержимое. Элемент со slot внутри компонента без теневого DOM виден как обычный: атрибут ничего не меняет.

Тег допустим там, где допустим текст: внутри p валидатор его принимает, а внутри ul отмечает ошибкой. Закрывающий </slot> обязателен.

Атрибуты тега slot: name и глобальный slot

У slot один собственный атрибут, а вставляемые элементы получают парный глобальный атрибут slot.

АтрибутГде записываютЧто делаетBlink / Firefox / Safari
nameНа теге slot внутри теневого DOMЗадаёт название слота. Слот без name или с name="" безымянный и берёт всё, что не нашло другого слота53 / 63 / 10
slotНа вставляемом элементе, прямом ребёнке компонентаВыбирает слот с таким же name. Пустое значение или отсутствие атрибута отправляет элемент в безымянный слот53 / 63 / 10
slot на теге slotНа слоте-посреднике внутри другого компонентаНазначает сам слот слоту внутреннего компонента53 / 63 / 10

В значении name допустимы пробелы и пустая строка: валидатор ошибки не даёт. Слот не имеет собственной роли для программ чтения экрана, поэтому смысл задают вставляемые элементы.

Как содержимое попадает в слот: правила распределения

Распределением слотов управляют несколько простых правил.

Элемент со slot="title" попадает в слот <slot name="title">. Элементы без атрибута и текст между ними попадают в безымянный слот. Распределяются только прямые дети компонента: вложенный элемент, даже со slot, в слот сам не попадает, а выводится вместе со своим родителем.

Элемент, для которого в теневом DOM нет слота с таким названием, не выводится, хотя остаётся на странице и находится через document.querySelector. То же происходит со всем остальным, если нет безымянного слота. Текст без тега можно направить только в безымянный слот.

Пример: что попадает в слот

Результат

Добавляйте элементы по одному. Слот title получает только прямых детей с slot=title, безымянный слот получает остальных и текст. Элемент с чужим названием слота остаётся на странице, но не выводится, а i внутри span в слот не попадает.

Если в слот попали несколько элементов, они выводятся в порядке документа. Если в теневом DOM два слота с одним name, все узлы получает первый, а второй остаётся пустым, пока первый на месте.

Пример: повторное название слота и порядок

Результат

Все три элемента попадают в первый слот с этим названием, второй остаётся пустым. Если первый слот убрать, элементы переходят во второй. Порядок внутри слота совпадает с порядком элементов в разметке.

Запасное содержимое слота и ловушка с пробелами

Запасное содержимое показывается, пока слот ничего не получил.

Всё, что записано внутри <slot>, служит запасным: текст, элементы, даже другой слот. Как только в слот попал хотя бы один узел, запасное содержимое пропадает, а когда узел убрали, возвращается. Метод assignedNodes() запасное содержимое в список не включает.

Ловушка связана с пробелами. Перенос строки и отступ между тегами компонента браузер считает текстовыми узлами и отдаёт безымянному слоту. Поэтому в такой записи запасное содержимое безымянного слота не появится: слот получил пустой текст.

HTML
<user-card>
  <span slot="title">Анна</span>
</user-card>

Пустой компонент и комментарий внутри него запасное содержимое не скрывают, а один пробел, перенос строки или любой текст скрывают. Чтобы запасное содержимое показывалось, теги внутри компонента записывают вплотную друг к другу.

Пример: запасное содержимое и пробелы

Результат

Пустой компонент и комментарий оставляют запасное содержимое. Один пробел или перенос строки между тегами браузер считает назначенным текстом, и запасное содержимое безымянного слота пропадает, хотя на экране ничего нет.

Стили слота: ::slotted, display: contents и наследование

Стили слота работают не так, как у обычных элементов.

У slot по умолчанию display: contents: собственного блока нет, поэтому фон, рамка, отступы и размеры на нём не рисуются. Если задать display: block, рамка появится.

Вставляемые элементы остаются элементами страницы, и стили страницы действуют на них как обычно. Изнутри компонента на них влияет ::slotted(селектор), но только на сам назначенный элемент, потомки не затрагиваются: ::slotted(b) не покрасит b внутри абзаца. Если стиль страницы и ::slotted задают одно свойство, побеждает стиль страницы (без !important; при !important порядок обратный). Псевдоэлемент ::slotted работает с Blink 50, Firefox 63 и Safari 10.

CSS
::slotted(p) {
  color: #C0392B;
}

slot {
  display: block;
}

Наследование идёт по итоговой структуре, где слот заменён своим содержимым. Поэтому color и font-style обёртки внутри компонента доходят до вставляемых элементов.

Пример: стили слота и ::slotted

Результат

Выберите правило и посмотрите на цвета и рамку. Стиль на самом slot не рисуется, пока у него display: contents, а стиль страницы для элемента побеждает ::slotted. Цвет, заданный обёртке внутри компонента, доходит до вставляемых элементов по наследованию.

JavaScript для слотов: assignedNodes и assignedSlot

Скрипт читает распределение через методы слота и свойство элемента.

Метод slot.assignedNodes() возвращает узлы, назначенные слоту, включая текст, а slot.assignedElements() возвращает только элементы. С параметром { flatten: true } пустой слот возвращает запасное содержимое, а слоты-посредники заменяются итоговыми узлами. Свойство element.assignedSlot у вставленного узла возвращает его слот, а у узла без слота возвращает null. Методы assignedNodes() и assignedSlot работают с Blink 53, Firefox 63 и Safari 10, а assignedElements() работает с Blink 65, Firefox 66 и Safari 12.1.

Пример: JavaScript для слотов

Результат

Метод assignedNodes() возвращает вставленные элементы страницы, запасное содержимое в них не входит. С flatten: true у пустого слота возвращается запасное содержимое. Вставленные элементы находятся поиском по странице, а запасное содержимое только поиском внутри теневого DOM.

Вставляемые элементы находит поиск по странице, а запасное содержимое и всё остальное внутри теневого DOM только поиск через shadowRoot компонента, если теневой DOM открыт. Событие, которое прошло через слот, содержит этот слот в composedPath(). Обработчик внутри компонента видит target без подмены, потому что кнопка относится к странице, а не к теневому DOM.

Пример: путь события через слот

Результат

Нажмите на кнопку. Событие идёт по пути кнопка, slot, обёртка внутри компонента, сам компонент, а обработчик внутри компонента видит кнопку без подмены, потому что она относится к странице.

Событие slotchange: когда слот узнаёт об изменениях

Событие slotchange сообщает, что список назначенных слоту узлов изменился.

Оно срабатывает на слоте асинхронно: после добавления узла, удаления, переноса в другой слот атрибутом slot и первого распределения, когда слот появился в теневом DOM вместе с уже добавленными детьми компонента. При переносе сообщают оба слота, прежний и новый. Изменение текста внутри уже назначенного элемента событие не вызывает, потому что список узлов тот же. Для таких изменений нужен MutationObserver. Событие есть в Blink 53, Firefox 63 и Safari 10.1.

JavaScript
slot.addEventListener("slotchange", () => {
  const items = slot.assignedElements();
  console.log(items.length);
});

Пример: журнал slotchange

Результат

Событие приходит асинхронно, после изменения. Добавление, перенос в другой слот и удаление его вызывают, а правка текста внутри уже назначенного абзаца нет. При смене slot сообщают оба слота.

Вложенные слоты и передача содержимого вниз

Слот можно записать среди дочерних элементов другого компонента, и тогда он передаёт содержимое во внутренний теневой DOM.

Если внутри теневого DOM внешнего компонента есть внутренний компонент, то среди его дочерних элементов записывают <slot name="in" slot="a">. Такой слот получает элементы внешнего компонента по своему name, а сам назначается слоту a внутреннего компонента. У внутреннего слота assignedNodes() возвращает слот-посредник, а assignedNodes({ flatten: true }) возвращает итоговый вставленный элемент. Если слот-посредник убрать, внутренний показывает своё запасное содержимое.

Пример: передача содержимого через два уровня теневого DOM

Результат

Слот-посредник записан среди дочерних элементов внутреннего компонента с атрибутом slot. Внутренний слот получает не вставляемый элемент, а этот slot, а с flatten: true возвращается итоговый вставляемый элемент. Если слота-посредника нет, показывается запасное содержимое.

Ручное назначение: slotAssignment и assign()

По умолчанию распределение идёт по названиям, но им можно управлять вручную.

Параметр slotAssignment: "manual" метода attachShadow() отключает распределение по name: узел попадает в слот только после slot.assign(узел). Узел принадлежит одному слоту, поэтому при новом вызове он пропадает из прежнего. Вызов assign() без аргументов очищает слот. Параметр работает с Blink 86, Firefox 92 и Safari 16.4, а метод assign() работает с Blink 92, Firefox 92 и Safari 16.4.

JavaScript
const root = host.attachShadow({
  mode: "open",
  slotAssignment: "manual",
});
root.querySelector("slot").assign(item);

Пример: ручное назначение

Результат

В ручном режиме названия слотов не работают: элемент попадает в слот только после вызова assign(). Элемент может быть назначен одному слоту, поэтому при переносе он пропадает из прежнего. Нужен Blink 92, Firefox 92 или Safari 16.4.

Почему тег slot не работает

Типичные причины, по которым слот ведёт себя не так, как ожидают, собраны в таблицу.

Что видноПричинаЧто делать
Светлый элемент не виденВ теневом DOM нет слота с таким name или нет безымянного слотаДобавить слот или поправить атрибут slot
Слот в обычной разметке ничего не делаетНет теневого DOM, а у slot display: contentsЗаписать слот в шаблон теневого DOM
Запасное содержимое не показываетсяМежду тегами внутри компонента есть пробелы или переносыЗаписать теги вплотную
Рамка и фон на slot не видныУ слота нет собственного блокаДобавить display: block
::slotted не красит вложенный элементСелектор находит только прямых детей компонентаПокрасить через наследование или стиль страницы
Правило ::slotted не действуетСтиль страницы для этого свойства сильнееИзменить стиль страницы
slotchange молчит при правке текстаСписок назначенных узлов не меняетсяСледить через MutationObserver
Второй слот с тем же name пустУзлы получает только первый слотДать слотам разные названия
Элемент со slot не выводится при manualВ ручном режиме названия не работаютВызвать assign()

Частые ошибки с тегом slot

Эти записи выглядят рабочими, но ведут себя иначе, чем ожидается.

ОшибкаЧто происходитКак исправить
<slot> в обычной разметкеСлот ничего не распределяет, виден только его запасЗаписать слот в шаблон теневого DOM
<ul><slot></slot></ul>Валидатор отмечает слот в ul ошибкойЗаписать слот внутри li
<div><span slot="title">Вложенный элемент не попадает в слот самСделать его прямым ребёнком компонента
slot.textContent = "Текст"Меняется запасное содержимое, а не содержимое слотаДобавить элемент в сам компонент
slot { border: 1px solid; }Рамки нет: у слота нет блокаДобавить display: block
::slotted(b)Не находит b внутри назначенного абзацаИспользовать наследование или стиль страницы
name="a" у двух слотовВторой слот остаётся пустымДать слотам разные названия
slot.assignedNodes()Запасное содержимое в список не входитДобавить { flatten: true }
Категория
Поделиться в социальных сетях

Отправить комментарий

0 Комментарии

Отправить комментарий (0)

#buttons=(Хорошо) #days=(20)

Мы используем файлы cookie для улучшения работы сайта. Подробнее
Хорошо
×