Перемещение элемента DOM без потери состояния

Веб-функции: moveBefore()

При изменении родительского элемента DOM с помощью традиционных методов, таких как appendChild() или insertBefore(), браузер неявно удаляет элемент из DOM, а затем вставляет его в новое место. Эта операция сбросит многие внутренние состояния, что приведет к перезагрузке элементов <iframe>, перезапуску CSS-анимаций и потере фокуса в полях ввода.

Чтобы переместить элемент, сохранив его состояние, используйте moveBefore() API. Этот метод выполняет атомарное перемещение, полностью минуя этапы удаления и вставки.

Перемещение элемента с состоянием

Используйте moveBefore() так же, как и insertBefore(). Для этого метода требуется два аргумента: узел, который нужно переместить, и узел, перед которым нужно вставить перемещаемый узел (или null, чтобы добавить его в конец нового родительского узла).

const newParent = document.getElementById('new-parent');
const elementWithState = document.getElementById('iframe-or-focused-input');

// MANDATORY: Use moveBefore to preserve state. 
// Passing null as the second argument appends the element to the end of newParent.
newParent.moveBefore(elementWithState, null);

Перемещение пользовательских элементов (веб-компонентов)

Если вы перемещаете пользовательские элементы с помощью moveBefore(), их методы жизненного цикла connectedCallback и disconnectedCallback не будут запущены.

Если при перемещении пользовательского элемента должна выполняться определенная логика, реализуйте метод connectedMoveCallback() в определении пользовательского элемента.

class MyCustomElement extends HTMLElement {
  connectedCallback() {
    // Runs on initial insertion.
  }
  
  connectedMoveCallback() {
    // Runs when the element is moved via moveBefore().
    // Use this to update state that depends on the new DOM location.
  }
}

Резервные стратегии

Поскольку moveBefore() – это прогрессивное улучшение, вы ДОЛЖНЫ использовать обнаружение функций перед вызовом, возвращаясь к традиционным операциям insertBefore() или appendChild() для более старых браузеров.

const targetParent = document.getElementById('target-container');
const nodeToMove = document.getElementById('moving-element');

// Check if moveBefore is supported on the Element prototype
if ('moveBefore' in Element.prototype) {
  targetParent.moveBefore(nodeToMove, null);
} else {
  // Fallback: traditional move. 
  // Note: This WILL reset <iframe>, animation, and focus state in unsupported browsers.
  targetParent.insertBefore(nodeToMove, null);
}