Элемент Template
Узнайте, как элемент HTML <template> хранит инертную разметку и как клонировать его содержимое с помощью JavaScript для создания динамических интерфейсов.
Элемент HTML <template> позволяет объявить фрагмент разметки на странице, который браузер разбирает, но не отображает. Содержимое остаётся в документе в инертном состоянии до тех пор, пока JavaScript не клонирует его и не вставит в живой DOM. Это делает <template> идиоматичным способом определить «шаблон» для повторяющихся элементов интерфейса — строк списков, карточек, модальных диалогов — без ручного создания узлов DOM или вставки HTML-строк в innerHTML.
В этой главе рассматривается, что делает содержимое шаблона особенным, как правильно клонировать и вставлять его, как привязывать события к клонированным узлам и какие подводные камни поджидают новичков. Шаблоны также являются строительным блоком Web Components, где они естественно сочетаются с пользовательскими элементами и Shadow DOM.
Понимание элемента Template
Элемент <template> служит контейнером для хранения HTML, который не отображается немедленно. Ключевая идея — инертность его содержимого:
- Оно не отображается — разметка никогда не рисуется, вне зависимости от CSS.
- Его ресурсы не загружаются —
<img>,<video>и<script>внутри шаблона не загружаются и не выполняются до тех пор, пока содержимое не будет клонировано в живой документ. - Скрипты внутри него не выполняются, а идентификаторы внутри него не конфликтуют с остальной частью страницы до момента активации.
Это принципиально отличается от скрытия элемента с помощью display: none. Элемент с display: none по-прежнему является частью живого DOM: его изображения загружаются, его скрипты выполняются, и document.getElementById находит элементы внутри него. Содержимое шаблона находится в отдельном дереве, основанном на фрагменте документа, поэтому ничего из этого не происходит до тех пор, пока вы явно не клонируете его.
<body>
<div>You won't see the template, as it's not activated using JS.</div>
<div id="template-container">
<template id="my-template">
<h1>Hidden!</h1>
</template>
</div>
</body>Клонирование шаблонов для динамического контента
Шаблон — это лишь чертёж: сам по себе он не производит видимого вывода. Чтобы использовать его, нужно прочитать его свойство content (тип DocumentFragment), клонировать этот фрагмент, заполнить данными и добавить клон на страницу. Поскольку для каждого экземпляра вы создаёте клон, один шаблон может породить сколько угодно независимых копий, каждая со своими данными.
Существует два способа клонирования:
template.content.cloneNode(true)— клонирует фрагмент на месте. Аргументtrueозначает глубокое клонирование (включая всех потомков); без него копируется только пустой фрагмент.document.importNode(template.content, true)— делает то же самое глубокое клонирование, но явно импортирует узлы в текущий документ. Это важно, когда шаблон находится в другом документе (например, содержимое, загруженное через<iframe>или<link rel="import">). Для шаблонов в том же документе оба способа эквивалентны;importNodeявляется более безопасным вариантом по умолчанию.
Всегда клонируйте содержимое шаблона перед вставкой. Если добавить template.content напрямую, исходные узлы перемещаются из шаблона в DOM — шаблон опустеет, и следующее клонирование не даст ничего. Клонирование сохраняет чертёж нетронутым для повторного использования.
<head>
<style>
.card {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
margin: 10px;
width: 200px;
}
.card h3 {
margin: 0;
}
</style>
</head>
<body>
<div id="template-container">
<!-- Template element -->
<template id="card-template">
<div class="card">
<h3 id="card-title">Title</h3>
<p id="card-content">Content goes here...</p>
</div>
</template>
</div>
<div id="card-container">
<!-- Cards will be inserted here -->
</div>
<script>
// Data for multiple cards
const cardData = [
{ title: 'Card 1', content: 'This is the first card.' },
{ title: 'Card 2', content: 'This is the second card.' },
{ title: 'Card 3', content: 'This is the third card.' }
];
// Function to create and insert cards
function createCards(data) {
const template = document.getElementById('card-template');
data.forEach(item => {
const clone = document.importNode(template.content, true);
// Customize the cloned content
clone.querySelector('#card-title').textContent = item.title;
clone.querySelector('#card-content').textContent = item.content;
// Insert the cloned content into the DOM
document.getElementById('card-container').appendChild(clone);
});
}
// Create cards with the provided data
createCards(cardData);
</script>
</body>Добавление интерактивности с помощью JavaScript
Свойство content шаблона возвращает DocumentFragment, содержащий инертную разметку. Вы можете запрашивать и читать его (template.content.querySelector(...)), не затрагивая живую страницу, но более чистый подход — сначала клонировать, а затем привязывать события к клону — так каждый экземпляр получает собственные обработчики. Подробнее об обработчиках событий читайте в разделе Обработка событий в DOM.
Пример ниже определяет два шаблона: кнопку и карточку с содержимым. При нажатии на кнопку клонируется шаблон карточки и каждый раз добавляется свежая копия.
<body>
<div id="template-container">
<!-- Button Template -->
<template id="button-template">
<button id="show-content-btn">Add a content card</button>
</template>
<!-- Content Template -->
<template id="content-template">
<div class="content">
<h2>Dynamic Content</h2>
<p>This content is added dynamically when the button is clicked.</p>
</div>
</template>
</div>
<div id="button-container">
<!-- Button will be inserted here -->
</div>
<div id="content-container">
<!-- Content will be displayed here -->
</div>
<script>
// Function to display template content
function displayTemplateContent() {
// Get the content template
const contentTemplate = document.getElementById('content-template');
// Access the .content property and clone it
const contentClone = document.importNode(contentTemplate.content, true);
// Display the cloned content
document.getElementById('content-container').appendChild(contentClone);
}
// Insert the button template into the DOM
function insertButton() {
// Get the button template
const buttonTemplate = document.getElementById('button-template');
const buttonClone = document.importNode(buttonTemplate.content, true);
// Add event listener to the button
buttonClone.querySelector('#show-content-btn').addEventListener('click', displayTemplateContent);
// Insert the button into the DOM
document.getElementById('button-container').appendChild(buttonClone);
}
// Call the function to insert the button when the page loads
insertButton();
</script>
</body>В этом примере:
- У нас два шаблона: один для кнопки (
button-template) и один для содержимого (content-template). - Функция
insertButtonклонирует шаблон кнопки и вставляет его в DOM. Также она прикрепляет обработчик события к кнопке, чтобы при нажатии вызывалась функцияdisplayTemplateContent. - Функция
displayTemplateContentклонирует шаблон содержимого и каждый раз при нажатии кнопки вставляет его в DOM. - Кнопка вставляется в DOM при загрузке страницы вызовом
insertButton.
Таким образом, когда вы нажмёте кнопку «Попробуйте сами», вы увидите кнопку с надписью «Add a content card». При каждом нажатии на страницу добавляется новая карточка из шаблона содержимого, демонстрируя динамическую вставку, управляемую действиями пользователя.
Распространённые ошибки
Несколько типичных ловушек становятся причиной большинства ошибок при работе с шаблонами:
- Забыли клонировать. Прямое добавление
template.contentопустошает шаблон. Всегда клонируйте сначала. - Запрос после добавления.
appendChild(clone)перемещает узлы фрагмента в DOM, поэтому они становятся дочерними элементами целевого узла. Читайте и изменяйте клон (clone.querySelector(...)) до его добавления — после этого фрагмент пуст. - Дублирующиеся идентификаторы. Идентификаторы внутри шаблона нормальны в инертном состоянии, но после многократного клонирования каждая копия несёт тот же идентификатор — а дублирующиеся идентификаторы являются недопустимым HTML. Предпочитайте классы, атрибуты
data-*или запросы на уровне клона вместоdocument.getElementByIdдля поиска по экземплярам. - Ожидание выполнения скриптов.
<script>внутри шаблона не выполняется при клонировании, если вы не пересоздадите его. Помещайте поведение в JavaScript, а не в разметку шаблона.
Поддержка браузерами
Элемент <template> является частью живого стандарта HTML и поддерживается во всех современных браузерах (Chrome, Firefox, Safari, Edge). Полифилл для современных браузеров не нужен, что делает его безопасным и независимым от сторонних библиотек решением для клиентских шаблонов.
Заключение
Элемент <template> предоставляет нативный способ без использования фреймворков определять повторно используемую разметку и создавать её экземпляры по требованию. Поскольку его содержимое инертно до клонирования, он исключает лишнюю отрисовку и загрузку ресурсов, характерную для скрытых элементов, а также обходит проблемы безопасности и разбора HTML-строк. Клонируйте с помощью cloneNode(true) или importNode, заполните клон данными, затем добавьте его — и вы получите эффективный паттерн, который масштабируется от одной карточки до целой системы компонентов. Чтобы увидеть шаблоны в рамках полноценного компонента, продолжайте с Пользовательскими элементами и Web Components.