Крайне важно иметь адаптивный пользовательский интерфейс, чтобы обеспечить последовательный и привлекательный пользовательский интерфейс. Поскольку сценарии DOM дороги и являются узким местом производительности в многофункциональных веб-приложениях. В этой статье рассматриваются распространенные методы оптимизации перерисовки и перекомпоновки DOM.
ДОМ
Объектная модель документа (DOM) — это представление данных объектов, составляющих структуру и содержимое документа в Интернете. В этом руководстве вы познакомитесь с DOM, посмотрите, как DOM представляет документ HTML в памяти и как использовать API для создания веб-контента и приложений.
DOM — это независимый от языка интерфейс приложения для работы с документами HTML и XML. Будучи независимыми, браузеры сохраняют реализацию DOM и JavaScript независимо друг от друга.
Это разделение позволяет другим технологиям и языкам, таким как VBScript, использовать DOM и функции рендеринга, которые может предложить Trident.
Например:
- Safari использует WebCore WebKit для DOM и рендеринга, хотя у него есть отдельный движок JavaScriptCore.
- Google Chrome также использует библиотеки WebCore из WebKit для рендеринга страниц, но реализует собственный механизм JavaScript под названием V8.
- Firefox использует движок рендеринга Gecko и SpiderMonkey в качестве движка JavaScript.
Наличие отдельных реализаций и взаимодействие друг с другом потребуют затрат. Хорошая аналогия — думать о JavaScript и DOM как об отдельных островах, соединенных одним мостом, поэтому каждый раз, когда нашему коду требуется доступ к острову DOM, ему нужно будет платить за проезд.
Рассмотрим этот пример:
function loop1() { let start = performance.now(); for (let i = 0; i < 1500; i++) { document.getElementById('spanElement').innerHTML += 'text'; } let end = performance.now(); console.log(`Loop1 execution took ${end - start} ms`); } function loop2() { let start = performance.now(); let contentToAppend = ''; for (let i = 0; i < 1500; i++) { contentToAppend += 'text'; } document.getElementById('spanElement').innerHTML += contentToAppend; let end = performance.now(); console.log(`Loop2 execution took ${end - start} ms`); }
В loop1
мы обращаемся к DOM 2 раза в каждом цикле, один раз для чтения содержимого HTML (innerHTML
) и второй раз для добавления текста.
Принимая во внимание, что в loop2
мы просто сохраняем обновленное содержимое и записываем значение в конец петли. Вполне естественно, что loop2
намного быстрее, чем loop1
Как правило, избегайте доступа к элементам DOM в циклах или просто минимизируйте доступ к элементам DOM, используйте локальную переменную для их кэширования, когда это возможно.
function slow() { let columns = document.getElementsByTagName('div'); let name = ''; for(let i = 0; i < columns.length; i++) { // accessing DOM element. name = document.getElementsByTagName('div')[i].nodeName; name = document.getElementsByTagName('div')[i].nodeType; name = document.getElementsByTagName('div')[i].tagName; } return name; } function fast() { // caching the results in a local variable. // Also, querySelector is faster than getElementsByTagName. // can you tell why ? let columns = document.querySelectorAll('div'); let name = ''; for (let i = 0, len = coll.length; i < len; i++) { let element = columns[i]; // accessing local variable is much faster than arrays. name = element.nodeName; name = element.nodeType; name = element.tagName; } return name; }
Стоимость намного больше, если мы модифицируем DOM, поскольку браузеру необходимо пересчитать изменения в геометрии страницы.
Перерисовка и оплавление
После того, как браузер загрузил все компоненты страницы, то есть HTML, JavaScript, таблицы стилей, изображения и т. д., он сохраняет информацию в двух внутренних структурах данных:
Дерево DOM, представляющее структуру страницы.
Дерево рендеринга показывает, как будут отображаться элементы DOM, таким образом, содержит всю информацию о стиле элементов. Дерево рендеринга имеет по крайней мере один узел для каждого узла в дереве DOM (за исключением скрытых узлов), и каждый узел/блок/кадр хранит информацию об отступах, полях, границах, положении и т. д. Когда изменение DOM влияет на геометрию элемента , браузер должен пересчитать геометрию элемента, а также геометрию других элементов, затронутых изменением. Таким образом, часть дерева рендеринга должна быть признана недействительной и перестроена. Этот процесс называется оплавлением.
После завершения перекомпоновки браузер перерисовывает затронутые части экрана в процессе, называемом перерисовкой.
Не все изменения DOM влияют на геометрию, например, изменение цвета фона узла не приводит к перекомпоновке, а вызывает перерисовку затронутой части.
Когда происходит перепрошивка?
- Добавляются или удаляются видимые элементы DOM.
- Размер/позиция/содержимое элемента изменены.
- Страница отображается изначально.
- Размер окна браузера изменен.
Из-за затрат, связанных с перекомпоновкой, браузер часто ставит эти изменения в очередь, а затем выполняет обновления пакетами. Однако эту очередь необходимо очищать всякий раз, когда запрашивается какая-либо информация о макете, например:
offsetTop
,offsetLeft
,offsetWidth
,offsetHeight
scrollTop
,scrollLeft
,scrollWidth
,scrollHeight
clientTop
,clientLeft
,clientWidth
,clientHeight
Таким образом, в процессе внесения изменений лучше не использовать эти запросы, так как это немедленно очистит очередь и приведет к многократному перекомпоновке.
Минимизация перерисовки и перекомпоновки
Поскольку перерисовки и перекомпоновки обходятся дорого, хорошей стратегией будет уменьшение количества изменений, объединение изменений в пакет и их одновременное применение.
// inefficient function updateStyle_slow() { let element = document.getElementById('navPanel'); element.style.border = '1px'; element.style.borderColor = 'red'; element.style.padding = '3px'; } // good practice function updateStyle_fast1() { let element = document.getElementById('navPanel'); element.style.cssText = 'border: 1px; border-color: red; padding: 3px'; } // or best to use css class function updateStyle_fast2() { let element = document.getElementById('navPanel'); element.classList.add('red-border'); }
Когда у нас есть много изменений, которые нужно применить к элементу DOM, мы можем уменьшить количество перерисовок и перекомпоновки:
- Отделение элемента от документа.
- Применить изменения.
- Добавьте элемент обратно в документ.
Этот процесс вызовет перекомпоновку на шаге 1 и шаге 3. Для достижения этих шагов мы можем сделать одно из следующих действий:
- Скройте элемент, примените изменения и снова отобразите его (используя
display:hidden
иdisplay:block
стили CSS). - Используйте фрагмент документа для построения поддерева вне DOM, а затем скопируйте его в документ. Перейдите по ссылке для получения дополнительной информации о фрагментах документа.
- Скопируйте исходный элемент во внедокументный узел, измените копию, а затем замените исходный элемент обратно.
Заключение
Чтобы оптимизировать производительность веб-приложения, нужно начать с профилирования количества перерисовок в консоли разработчика. А потом искать места в коде, которые можно оптимизировать с помощью упомянутых приемов.
Взгляните на эту ссылку, чтобы узнать больше о профилировании производительности в консоли разработчика: https://www.youtube.com/watch?v=KWM5wxlDuis