DocumentFragment

как делать пакетные DOM-вставки аккуратнее!

JS

Когда элементы добавляются в DOM по одному, браузеру приходится постоянно обновлять структуру документа. На небольших объёмах это почти незаметно, но при рендере больших списков или сложных интерфейсов такие операции могут начать стоить дороже. Для таких случаев в DOM API есть DocumentFragment.

Создаём fragment:

const fragment = document.createDocumentFragment();

Это временный контейнер, который существует вне основного DOM-дерева. Пока элементы находятся внутри fragment — они не участвуют в рендеринге страницы. Можно спокойно собрать структуру в памяти, а потом вставить всё одной операцией. Пример — массовое добавление элементов:

const fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');

  li.textContent = `Item ${i}`;

  fragment.appendChild(li);
}

document
  .querySelector('.list')
  .appendChild(fragment);

Здесь элементы сначала собираются внутри fragment, а затем одной вставкой попадают в основной DOM. Такой подход уменьшает количество промежуточных вставок в live DOM и делает массовый рендер более предсказуемым.

Пример через append():

const fragment = document.createDocumentFragment();

users.forEach(user => {
  const item = document.createElement('div');

  item.className = 'user';
  item.textContent = user.name;

  fragment.append(item);
});

container.append(fragment);

append() тоже работает с DocumentFragment. По смыслу разницы почти нет — append() просто современнее и удобнее. Плюс он умеет принимать строки и несколько узлов сразу.

Пример с переносом существующих элементов:

const fragment = document.createDocumentFragment();

document
  .querySelectorAll('.item')
  .forEach(el => {
    fragment.appendChild(el);
  });

newContainer.appendChild(fragment);

Здесь элементы не копируются, appendChild() физически переносит DOM-узлы в новый контейнер. Один и тот же элемент не может одновременно находиться в двух местах дерева.

Ещё один частый кейс — работа с template:

const template = document.querySelector('#card');

const fragment = document.createDocumentFragment();

data.forEach(item => {
  const cardFragment = template.content
    .cloneNode(true);

  cardFragment
    .querySelector('.title')
    .textContent = item.title;

  fragment.appendChild(cardFragment);
});

container.appendChild(fragment);

template.content уже является DocumentFragment, поэтому cloneNode(true) возвращает готовый набор DOM-узлов, который удобно изменять перед вставкой.

И ещё один момент:

const fragment = document.createDocumentFragment();

fragment.appendChild(
  document.createElement('div')
);

container.appendChild(fragment);

console.log(fragment.childNodes.length); // 0

После вставки содержимое fragment переносится в DOM, а сам fragment остаётся пустым. По сути это временный контейнер для группы узлов.

Важно понимать, что DocumentFragment — не полноценный HTML-элемент:

console.log(
  fragment instanceof HTMLElement
); // false

У него нет собственного layout, стилей или визуального отображения. Но при больших объёмах DOM, сложных компонентах или частых обновлениях интерфейса DocumentFragment всё ещё остаётся удобным способом собрать структуру отдельно от основного DOM и вставить её одним действием.

Похожие записи