Вы действительно понимаете reflow и redraw?

браузер

Автор: Чен Цзигэн

Можно сказать, что перекомпоновка и перерисовка — это два слова, которые часто слышит каждый веб-разработчик, но многие люди могут не знать точно, что делают эти два шага. Недавно у меня была возможность провести некоторое исследование, прочитать несколько блогов и книг, систематизировать контент и объединить несколько примеров, и я написал эту статью, надеясь всем помочь.

Время чтения составляет около 15 ~ 18 минут

Процесс рендеринга в браузере

В этой статье сначала объясняется перекомпоновка и перерисовка от начала до конца процесса рендеринга браузера.Если вы хотите непосредственно увидеть, как уменьшить перекомпоновку и перерисовку и оптимизировать производительность, вы можете перейти к концу. (Этот процесс рендеринга исходит изMDN)

webkit渲染过程

На картинке выше мы видим, что процесс рендеринга в браузере выглядит следующим образом:

  1. Разборные HTML, дерево DOM генерируется, анализируют CSS, генерируют CSSOM дерево
  2. Объедините дерево DOM и дерево CSSOM для создания дерева рендеринга.
  3. Макет (перекомпоновка): согласно сгенерированному дереву рендеринга выполните перекомпоновку (макет), чтобы получить геометрическую информацию (положение, размер) узла.
  4. Покраска (перерисовка): По геометрической информации, полученной из дерева рендеринга и перекомпоновки, получаются абсолютные пиксели узла
  5. Отображение: отправка пикселей на графический процессор для отображения на странице. (На этом этапе на самом деле много контента. Например, несколько слоев синтеза будут объединены в один слой на графическом процессоре и отображены на странице. Принцип аппаратного ускорения css3 заключается в создании нового слоя синтеза. Мы тут не развернуть, а возможности будут позже. буду писать в блог)

Процесс рендеринга выглядит достаточно просто, давайте подробнее рассмотрим, что именно делает каждый шаг.

Генерация дерева рендеринга

生成渲染树

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

  1. Пройдите каждый видимый узел, начиная с корня дерева DOM.
  2. Для каждого видимого узла найдите соответствующие правила в дереве CSSOM и примените их.
  3. На основе каждого видимого узла и соответствующего ему стиля в комбинации создается дерево рендеринга.

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

  • Некоторые узлы, которые не будут отображать выходные данные, такие как скрипт, мета, ссылка и т. д.
  • Некоторые узлы скрыты через css. Например, отображение: нет. Обратите внимание, что узлы, скрытые видимостью и непрозрачностью, все равно будут отображаться в дереве рендеринга. Только узлы с display:none не будут отображаться в дереве рендеринга.

Из приведенного выше примера мы можем видеть, что стиль тега span имеет display:none, поэтому он не попадает в дерево рендеринга.

Примечание. Дерево рендеринга содержит только видимые узлы.

переплавка

Ранее при построении дерева рендеринга мы комбинировали видимые DOM-узлы и соответствующие им стили, но нам также нужно было рассчитать их точное положение и размер в пределах области просмотра устройства.Этап этого расчета — reflow.

Чтобы определить точный размер и положение каждого объекта на веб-сайте, браузер переходит от корня дерева рендеринга, что мы можем представить в следующем примере:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

Мы видим, что первый div устанавливает размер отображения узла равным 50% ширины окна просмотра, а второй div устанавливает его размер равным 50% родительского узла. На этапе перекомпоновки нам нужно преобразовать его в фактическое значение в пикселях в соответствии с конкретной шириной области просмотра. (Как показано ниже)

перерисовать

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

Теперь, когда мы знаем процесс рендеринга в браузере, давайте обсудим, когда происходит перерисовка с переформатированием.

Когда происходит перерисовка оплавления

Как мы знаем ранее, этот этап перекомпоновки в основном предназначен для расчета положения и геометрической информации узлов, поэтому при изменении макета страницы и геометрической информации требуется перекомпоновка. Например следующая ситуация:

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

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

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

Механизм оптимизации браузера

Современные браузеры достаточно умны, чтобы оптимизировать процесс перекомпоновки, ставя изменения в очередь и выполняя их пакетами, так как каждая перекомпоновка требует дополнительных вычислительных затрат. Браузер поместит операцию модификации в очередь, и очередь не будет очищена до тех пор, пока не пройдет определенный период времени или пока операция не достигнет порогового значения. но!Когда вы получите операцию с информацией о макете, она заставит очередь обновиться., например, когда вы получаете доступ к следующим свойствам или используете следующие методы:

  • Offsettop, Offsetleft, OffsetWidth, OffsetHeight
  • scrollTop, scrollLeft, scrollWidth, scrollHeight
  • clientTop, clientLeft, clientWidth, clientHeight
  • getComputedStyle()
  • getBoundingClientRect
  • В частности, вы можете посетить этот веб-сайт:gist.GitHub.com/Пол Айриш/5…

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

Уменьшите количество перекомпоновок и перерисовок

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

Минимизируйте перерисовки и перекомпоновки

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

const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';

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

Поэтому мы можем объединить все изменения и обрабатывать их последовательно, например, мы можем сделать следующее:

  • использовать cssтекст

    const el = document.getElementById('test');
    el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
    
  • Изменить класс CSS

    const el = document.getElementById('test');
    el.className += ' active';
    

Пакетное редактирование DOM

Когда нам нужно внести ряд изменений в DOM, мы можем уменьшить количество перекомпоновок и перерисовок, выполнив следующие шаги:

  1. Исключить элемент из документооборота
  2. изменить его несколько раз
  3. Верните элемент обратно в документ.

Первый и третий шаги процесса могут вызвать перекомпоновку, но после первого шага все изменения в DOM не приведут к перерисовке перекомпоновки, поскольку ее больше нет в дереве рендеринга.

Есть три способа вывести DOM из документооборота:

  • Скрыть элементы, применить изменения, повторно отобразить
  • Используйте фрагмент документа, чтобы построить поддерево за пределами текущего DOM и скопировать его обратно в документ.
  • Скопируйте исходный элемент в узел вне документа, измените узел, а затем замените исходный элемент.

Предположим, мы хотим выполнить фрагмент кода, который вставляет узлы пакетами:

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
    	li = document.createElement('li');
        li.textContent = 'text';
        appendToElement.appendChild(li);
    }
}

const ul = document.getElementById('list');
appendDataToElement(ul, data);

Если мы сделаем это напрямую, это приведет к тому, что браузер один раз перестроится, потому что каждый раз в цикле вставляется новый узел.

Мы можем использовать эти три способа оптимизации:

Скрыть элементы, применить изменения, повторно отобразить

Это создаст два перекомпоновки при отображении и скрытии узлов.

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
    	li = document.createElement('li');
        li.textContent = 'text';
        appendToElement.appendChild(li);
    }
}
const ul = document.getElementById('list');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

Используйте фрагмент документа, чтобы построить поддерево за пределами текущего DOM и скопировать его обратно в документ.

const ul = document.getElementById('list');
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
ul.appendChild(fragment);

Скопируйте исходный элемент в узел вне документа, измените узел, а затем замените исходный элемент.

const ul = document.getElementById('list');
const clone = ul.cloneNode(true);
appendDataToElement(clone, data);
ul.parentNode.replaceChild(clone, ul);

Для трех вышеуказанных случаев я написалdemoПроверьте производительность до и после модификации на сафари и хроме. Однако результаты эксперимента не очень удовлетворительны.

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

Избегайте запуска синхронных событий макета

Как мы упоминали выше, когда мы обращаемся к некоторым атрибутам элемента, это заставит браузер принудительно очистить очередь и принудительно синхронизировать макет. Например, предположим, что мы хотим присвоить ширину массива тегов p ширине элемента, мы могли бы написать такой код:

function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

Этот код кажется хорошим, но на самом деле он может вызвать большие проблемы с производительностью. В каждом цикле значение свойства offsetWidth поля считывается, а затем используется для обновления свойства ширины тега p. Это приводит к тому, что в каждом цикле браузер должен сделать так, чтобы операция обновления стиля в предыдущем цикле вступила в силу, прежде чем он сможет ответить на операцию чтения стиля этого цикла.Каждый цикл заставит браузер обновить очередь. Мы можем оптимизировать как:

const width = box.offsetWidth;
function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}

Аналогично, я также написалdemoЧтобы сравнить различия в производительности в обоих. Вы можете открыть эту демонстрационную версию самостоятельно. Этот разрыв в контрастности более очевиден.

Для сложных анимаций используйте абсолютное позиционирование, чтобы они не попадали в поток документов.

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

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

image-20181210223750055

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

аппаратное ускорение css3 (ускорение графического процессора)

Вместо того, чтобы думать о том, как уменьшить перерисовку перерисовки, мы бы предпочли вообще не перерисовывать перерисовку. В это время на сцену выходит аппаратное ускорение css3! !

Сфокусируйся на:

1. При использовании аппаратного ускорения css3 анимации, такие как преобразование, непрозрачность и фильтры, не будут вызывать перекомпоновку и перерисовку.

2. Для других свойств анимации, таких как background-color, это по-прежнему будет вызывать перекомпоновку и перерисовку, но все же может повысить производительность этих анимаций.

В этой статье обсуждается только то, как его использовать, и пока не рассматривается его принцип.Когда у меня будет время, я начну другую статью, чтобы объяснить это.

как использовать

Общие свойства css, запускающие аппаратное ускорение:

  • transform
  • opacity
  • filters
  • Will-change

Эффект

мы можем взглянутьпример. Я использовал производительность Chrome, чтобы зафиксировать перерисовку анимации в течение определенного периода времени Фактический результат выглядит следующим образом:

image-20181210225609533

Как видно из рисунка, пока идет анимация, перерисовки перерисовки не происходит. Вы также можете провести эксперимент самостоятельно, если вам интересно.

фокус

  • При использовании аппаратного ускорения css3 анимации, такие как преобразование, непрозрачность и фильтры, не будут вызывать перекомпоновку и перерисовку.
  • Для других свойств анимации, таких как background-color, это по-прежнему вызовет перерисовку перерисовки, но все же может повысить производительность этих анимаций.

Яма аппаратного ускорения css3

Конечно, любые хорошие вещи будут иметь соответствующую цену, слишком много. Есть еще ямки в аппаратном ускорении css3:

  • Если вы используете аппаратное ускорение CSS3 для слишком большого количества элементов, это приведет к большому объему памяти и проблемам с производительностью.
  • Рендеринг шрифтов на графическом процессоре сделает сглаживание неэффективным. Это связано с тем, что алгоритмы GPU и CPU различаются. Поэтому, если вы не отключите аппаратное ускорение в конце анимации, произойдет размытие шрифта.

Суммировать

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

использованная литература


Технологический еженедельник IVWEBШок в сети, обратите внимание на публичный номер: сообщество IVWEB, регулярно каждую неделю публикуйте качественные статьи.

  • Сборник статей еженедельника:weekly
  • Командные проекты с открытым исходным кодом:Feflow