Передовые методы работы с интерфейсом (1) — манипулирование DOM

внешний интерфейс браузер Google Ресурсы изображений

предисловие

Эта статья представляет собой недавний опыт блоггера по решению некоторых проблем с задержкой веб-сайта.
Оригинальный адрес:Передовые методы работы с интерфейсом (1) — манипулирование DOM, Если вы найдете это полезным, вы можете дать звезду, спасибо.
Добавить Автора

1. Принцип рендеринга в браузере

Прежде чем говорить о лучших методах работы с DOM, давайте познакомимся с основными принципами рендеринга в браузере. Основной процесс рендеринга браузера и отображения веб-страниц можно примерно представить на следующем рисунке:

图:WebKit 主流程
Рисунок: основной процесс WebKit

Делится на следующие четыре шага:

  • Разбор HTML (парсер HTML)

  • Построить DOM-дерево (DOM-дерево)

  • Построение дерева рендеринга (Дерево рендеринга)

  • Отрисовка дерева рендеринга (Painting)

Браузер запрашивает синтаксический анализ (парсер) документа HTML и преобразование каждого тега в узел DOM (дерево DOM) один за другим. Он также анализирует внешние файлы CSS и данные стиля в элементах стиля. Эта информация о стиле с визуальными инструкциями в HTML будет использоваться для создания другой древовидной структуры: дерева рендеринга. Дерево визуализации содержит несколько прямоугольников с визуальными свойствами, такими как цвет и размер. Порядок расположения прямоугольников — это порядок, в котором они будут отображаться на экране. После того, как дерево рендеринга построено, оно переходит в процесс «макета», где каждому узлу присваивается точная координата, где он должен отображаться на экране. Следующим этапом является рисование — браузер просматривает дерево рендеринга, и каждый узел рисуется внутренним слоем пользовательского интерфейса.

Важно отметить, что это постепенный процесс. Для лучшего взаимодействия с пользователем браузеры стремятся отображать контент на экране как можно быстрее. Не нужно ждать, пока весь HTML-документ будет проанализирован, чтобы начать построение дерева рендеринга и настройку макета. Непрерывно получая и обрабатывая остальной контент из Интернета, браузер анализирует и отображает часть контента.

2. Перекрашивает и переплавляет

Repaint: это можно понимать как перерисовку или перерисовку.Когда некоторым элементам в дереве рендеринга необходимо обновить свои свойства, эти свойства влияют только на внешний вид и стиль элементов, но не на макет, например на изменение цвета фона. Это называется перекраска.
Reflows: может пониматься как перекомпоновка, компоновка или перекомпоновка, когда часть (или все) дерева рендеринга необходимо перестроить из-за изменений размера, компоновки и сокрытия элементов. Это называется перекомпоновкой или повторной компоновкой.

Когда сработает перекомпоновка или перерисовка?

Изменение всего, что используется для построения дерева рендеринга, может привести к перерисовке или перекомпоновке, например:
1. Добавляйте, удаляйте, обновляйте узлы DOM
2. Скрыть узел с отображением: нет (перекомпоновать и перерисовать) или видимость: скрыто (только перерисовать, потому что геометрия не меняется)
3. Добавьте таблицу стилей и настройте свойства стиля.
4. Измените размер окна, измените размер шрифта
5. Рендеринг инициализации страницы
6. Перемещение элементов DOM
. . .

Давайте рассмотрим несколько примеров:

var bstyle = document.body.style; // cache

bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; // another reflow and a repaint

bstyle.color = "blue"; // repaint only, no dimensions changed
bstyle.backgroundColor = "#fad"; // repaint

bstyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

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

3. Лучшие практики манипулирования DOM

Перерисовка страницы и перекомпоновка из-за манипуляций с DOM неизбежны, но есть несколько рекомендаций, которым можно следовать, чтобы свести к минимуму перерисовку и перекомпоновку. Вот некоторые конкретные практики:

3.1. Объединение нескольких операций DOM

// bad
var left = 10,
  top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// better 
el.className += " theclassname";
// better
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

Поскольку перерисовки и перекомпоновки, связанные с изменениями дерева рендеринга, очень дороги, современные браузеры имеют оптимизацию производительности для частых перерисовок и перекомпоновок. Одна из стратегий заключается в том, что браузер создаст очередь изменений, требуемых сценарием, и будет выполнять их пакетами. Таким образом, несколько изменений, для каждого из которых требуется перекомпоновка, будут объединены, и будет вычисляться только одна перекомпоновка. Браузеры могут добавлять изменения в очередь, а затем обновлять очередь по прошествии определенного времени или определенного количества изменений (эта оптимизация есть не во всех браузерах. Рекомендуемый способ — максимально сочетать операции DOM). Но иногда скрипт может помешать браузеру оптимизировать Reflow и заставить его сбросить очередь и выполнить все массовые изменения. Это происходит, когда вы запрашиваете информацию о стиле, как показано ниже (не все включено). Смотри ниже:

Все вышеперечисленное в основном является запросом информации о стиле для узла, и браузер должен предоставить самые последние значения. Для этого необходимо применить все запланированные изменения, очистить очередь, принудительно выполнить перекомпоновку. Поэтому при больших пакетах операций DOM следует избегать получения информации о расположении элементов DOM, чтобы не нарушить оптимизацию браузера для больших пакетов операций DOM. Если вам нужна эта информация о макете, лучше всего получить ее до манипуляций с DOM.

//bad
var bstyle = document.body.style;

bodystyle.color = 'red';
tmp = computed.backgroundColor;

bodystyle.color = 'white';
tmp = computed.backgroundImage;

bodystyle.color = 'green';
tmp = computed.backgroundAttachment;

//better
tmp = computed.backgroundColor;
tmp = computed.backgroundImage;
tmp = computed.backgroundAttachment;

bodystyle.color = 'yellow';
bodystyle.color = 'pink';
bodystyle.color = 'blue';

3.2 Измените элементы DOM после их удаления из дерева рендеринга.

(1) Используйте фрагменты документа
DocumentFragments — это узлы DOM. Они не являются частью основного дерева DOM. Обычно используется для создания фрагмента документа, добавления элементов к фрагменту документа, а затем добавления фрагмента документа к дереву DOM. В DOM-дереве фрагмент документа заменяется всеми его дочерними элементами. Поскольку фрагмент документа существует в памяти, а не в дереве DOM, вставка дочерних элементов во фрагмент документа не приведет к переформатированию страницы. Конечно, последний шаг добавления фрагмента документа на страницу все равно вызовет перекомпоновку.

var fragment = document.createDocumentFragment();
// 一些基于fragment的大量DOM操作
...
document.getElementById('myElement').appendChild(fragment);

(2) Скройте элемент, установив для стиля отображения элемента DOM значение none.
Принцип заключается в том, чтобы сначала скрыть элемент, затем выполнить операции DOM на основе элемента и отобразить элемент после большого количества операций DOM.

var myElement = document.getElementById('myElement');
myElement.style.display = 'none';
// 一些基于myElement的大量DOM操作
...
myElement.style.display = 'block';

(3) Клонировать элементы DOM в память
Этот метод заключается в клонировании элемента DOM на странице в память, а затем работе с клонированным элементом в памяти.После завершения операции клонированный элемент используется для замены исходного элемента DOM на странице.

var old = document.getElementById('myElement');
var clone = old.cloneNode(true);
// 一些基于clone的大量DOM操作
...
old.parentNode.replaceChild(clone, old);

3.3 Используйте локальные переменные для кэширования информации о стиле

Получение информации о стиле DOM приведет к снижению производительности, поэтому, если есть вызовы цикла, лучше всего попытаться кэшировать эти значения в локальных переменных.

// bad
function resizeAllParagraphsToMatchBlockWidth() {
    for (var i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

// better
var width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth() {
    for (var i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}

3.4 Установите фиксированное положение элементов DOM с эффектами анимации

Использование абсолютного позиционирования делает элемент прямым дочерним элементом тела в дереве рендеринга, поэтому, когда он анимируется, он не влияет на слишком много других узлов.

4. Конкретные примеры

4.1 Пакетная обработка и перекомпоновка в браузере

Далее поясню на конкретном примере, адрес ссылки такой:reflow.

Код для первого клика выглядит следующим образом:

function touch() {
    bodystyle.color = 'red';
    bodystyle.padding = '1px';
    tmp = computed.backgroundColor;
    bodystyle.color = 'white';
    bodystyle.padding = '2px';
    tmp = computed.backgroundImage;
    bodystyle.color = 'green';
    bodystyle.padding = '3px';
    tmp = computed.backgroundAttachment;
}

Код для второго клика выглядит следующим образом:

function touchlast() {
    tmp = computed.backgroundColor;
    tmp = computed.backgroundImage;
    tmp = computed.backgroundAttachment;
    bodystyle.color = 'yellow';
    bodystyle.padding = '4px';
    bodystyle.color = 'pink';
    bodystyle.padding = '5px';
    bodystyle.color = 'blue';
    bodystyle.padding = '6px';
}

Ниже мы будем использовать инструменты Google, чтобы увидеть, в чем сходство и различие между этими двумя операциями.

4.1.1 Сначала откройте указанную выше ссылку в Google Chrome. Нажмите F12, чтобы переключиться на параметр «Производительность».

Результат выглядит следующим образом:

4.1.2. Нажмите ctrl + E (или щелкните маленькую точку), чтобы начать запись, щелкните область тела и нажмите «Стоп», чтобы остановить запись после того, как текст станет зеленым.

Результат выглядит следующим образом:

4.1.3.Выделите часть, где синий (куча js) внезапно поднимается на картинке выше, указывая на процесс щелчка по телу только что, и прокрутите мышь, чтобы увеличить основной поток.

Результат такой, обратите внимание куда указывает стрелка:

На изображении выше легко увидеть, что браузер вычисляет стиль 3 раза во время клика по телу.

4.1.4. Нажмите кнопку очистки рядом с точкой, чтобы очистить ее, и повторяйте вышеуказанные операции, пока текст не станет синим, чтобы остановить:

Результат такой, обратите внимание куда указывает стрелка:

Из приведенного выше рисунка легко увидеть, что браузер вычисляет стиль только один раз при повторном щелчке по телу. Таким образом, мы можем подтвердить наш вывод о пакетной обработке браузера выше. Неоптимизированное время рендеринга составляет 0,4 мс, в то время как оптимизированное время рендеринга составляет 0,3 мс. Существует такая большая разница в таком небольшом выполнении js. В некоторых анимациях, которые часто работают с DOM, теряется производительность. Вполне возможно, что будет пример анимации сзади, которую можно увидеть интуитивно.

4.2 Влияние, вызванное частым обратным потоком

В примере, приведенном Google Docs, адрес ссылки выглядит следующим образом:animation.

Код до оптимизации:

var pos = m.classList.contains('down') ?
    m.offsetTop + distance : m.offsetTop - distance;
    if (pos < 0) pos = 0;
    if (pos > maxHeight) pos = maxHeight;
        m.style.top = pos + 'px';
    if (m.offsetTop === 0) {
        m.classList.remove('up');
        m.classList.add('down');
    }
    if (m.offsetTop === maxHeight) {
        m.classList.remove('down');
        m.classList.add('up');
    }

Оптимизированный код:

var pos = parseInt(m.style.top.slice(0, m.style.top.indexOf('px')));
    m.classList.contains('down') ? pos += distance : pos -= distance;
    if (pos < 0) pos = 0;
    if (pos > maxHeight) pos = maxHeight;
    m.style.top = pos + 'px';
    if (pos === 0) {
        m.classList.remove('up');
        m.classList.add('down');
    }
    if (pos === maxHeight) {
        m.classList.remove('down');
        m.classList.add('up');
    }

Сначала дросселируйте процессор, а затем добавляйте больше маленьких значков «Google», пока скорость значков значительно не замедлится, а затем нажмите кнопку оптимизации «Оптимизировать», вы отчетливо почувствуете разницу. Что касается того, как дросселировать процессор и позиционирование, пожалуйста, обратитесь к другой моей статье.Какой? Страница застряла? Операция медленная?.