От автора: несмотря на то, что страницы порой весят до 2Мб, производительность остается горячей темой для обсуждения. Если у вас отличное веб-приложение, и оно нравится пользователям, то высокая конверсия вам обеспечена.
И все-таки я виноват в том, что часто добавляю CSS3 анимацию или манипулирую множеством элементов DOM, не задумываясь о последствиях. При применении визуальных эффектов на сайте употребляются два термина:
Repaints (Перерисовка)
Repaint возникает в случаях, если были изменены визуальные составляющие элемента, не затрагивающие его разметку. К примеру, opacity, background-color, visibility и outline. Repaint достаточно затратная штука с точки зрения производительности, т.к. браузер должен проверить видимость всех узлом DOM–под одним измененным элементом могут стать видимыми еще один или два.
Reflows (Перекомпановка)
Reflow же еще больше воздействуют на производительность. Во время этого процесса происходит перерасчет позиций и размеров всех элементов, что приводит к полной или частичной повторной отрисовке страницы. Изменения в одном элементе могут повлиять на все дочерние, родительские или смежные элементы.
Оба процесса блокируют браузер; и не пользователь, и не ваше веб-приложение не смогут ничего сделать во время reflow и repaint. В крайних случаях CSS эффекты могут затормозить выполнение JavaScript’а. Это одна из причин, почему вы сталкиваетесь с дергающейся прокруткой страницы и неотзывчивым интерфейсом.
Полезно знать, в каких случаях запускается reflow:
При добавлении, удалении или изменении видимого элемента DOM
Первый случай самый очевидный; использование JavaScript для изменения DOM страницы вызывает reflow.
При добавлении, удалении или изменении CSS стилей
Этот случай похож на первый, изменение стилей напрямую или изменение классов может повлиять на весь макет страницы. Изменив ширину элемента, вы можете повлиять на все элементы того же уровня в DOM и окружающие его элементы.
CSS3 анимация и свойство transition
Каждый кадр анимации вызывает reflow.
Использование offsetWidth и OffsetHeight
Странно, но при попытке считать значения offsetWidth и offsetHeight элемента для их вычисления может быть запущен reflow.
Действия пользователя
И наконец пользователь может вызвать reflow, активировав состояние :hover, при вводе текста в поля, при изменении размера окна, изменении размера шрифта, переключении стилей или шрифтов.
Используйте методику «Лучшей практики» при создании макета
Не могу поверить, что мне еще нужно об этом говорить в 2015 году, но не используйте инлайновые стили или таблицы в своих макетах!
Инлайновые стили срабатывают только после окончательной загрузки HTML и вызывают дополнительный reflow. Таблицы снижают производительность, так как парсер не один раз проходится по таблице для вычисления размера ячейки. Table-layout: fixed может помочь при представлении табличных данных, так как ширина столбцов будет изменяться в зависимости от содержания строки заголовка.
Flexbox на главной странице сайта также может привести к падению производительности, так как позиция и размеры ячейки могут измениться после окончательной загрузки HTML.
Максимально сократите набор правил CSS
Чем меньше правил CSS, тем быстрее reflow. Также по возможности следует избегать сложных CSS селекторов.
Особенно проблематично если вы используете фреймворк, например, Bootstrap – несколько сайтов используют одновременно больше половины всех стилей. Такие инструменты как UnusedCSS, uCSS, grunt-uncss, и gulp-uncss могут значительно сжать описание стилей и их вес.
Минимизируйте провалы в DOM
С этим будет чуть сложнее – уменьшите размер вашего DOM и количества узлов в каждой ветке. Чем меньше и мельче ваш документ, тем быстрее будет reflow. Может быть, можно удалить ненужные элементы-обертки, если вы не поддерживаете старые браузеры.
Как можно сильнее углубите изменения классов в DOM
Сделайте так, что бы при изменении стилей в классах, эти изменения затрагивали как можно более глубокие элементы в DOM (т.е. для тех элементов, у которых нет большого количества дочерних элементов, которые так же имеют большую вложенность). Данный шаг может сократить площадь reflow до пары узлов. По сути, если эффект на дочерние элементы минимален, необходимо применять изменения в классах только для родителей, таких как блоки-обертки.
Удалите сложную анимацию из потока reflow
Добейтесь того, чтобы анимация применялась к элементам вне DOM. Этого можно достичь с помощью position: absolute или position: fixed. В таком случае изменение размеров и положения элемента не влияют на остальные элементы в документе.
Изменяйте элементы в скрытом состоянии
Скрытые при помощи display: none элементы не повлекут за собой repaint или reflow во время изменения их стилей. На практике, перед тем, как сделать элемент видимым, старайтесь изменить необходимые для него стили.
Обновляйте элементы в Batch
Повысить производительность можно путем обновления всего DOM с помощью всего одной операции. В этом примере reflow вызывается 3 раза:
1 2 3 4 |
var myelement = document.getElementById('myelement'); myelement.width = '100px'; myelement.height = '200px'; myelement.style.margin = '10px'; |
Мы можем уменьшить количество reflow до одного.
1 2 3 4 5 6 7 |
var myelement = document.getElementById('myelement'); myelement.classList.add('newstyles'); .newstyles { width: 100px; height: 200px; margin: 10px; } |
Также можно свести к минимуму необходимость лезть в DOM. Предположим, что вы захотели создать такой маркированный список:
item 1
item 2
item 3
Добавление каждого элемента по отдельности повлечет за собой аж 7 reflow – один при вставке тега ul, 3 при вставке liи еще 3 при вставке текста к пунктам списка. Хотя, это же можно сделать и с одним reflow, если использовать фрагмент DOM и заранее создать в памяти необходимые узлы.
1 2 3 4 5 6 7 8 9 10 11 |
var i, li, frag = document.createDocumentFragment(), ul = frag.appendChild(document.createElement('ul')); for(i = 1; i <= 3; i++) { li = ul.appendChild(document.createElement('li')); li.textContent = 'item '+ i; } document.body.appendChild(frag); |
Ограничьте задействованные элементы
Избегайте ситуаций, когда в reflow может быть вовлечено огромное количество элементов. Можно рассмотреть систему вкладок (табов), по клику на таб видимым становится соответствующий блок. Окружающие элементы будут вовлечены в процесс в том случае, если все блоки будут иметь разную высоту. Производительность можно повысить, установив фиксированную высоту для контейнера, или удалив этот элемент.
Признайте тот факт, что плавность анимации влияет на производительность
Передвигать элемент по пикселю в секунду может смотреться красиво и гладко, но для слабых устройств это затруднительно. Перемещение элемента на 4 пикселя в секунду снижает reflow в 4 раза, и почти незаметно для глаза.
Анализируйте Repaint с помощью инструментария браузера
Все современные браузеры предоставляют разработчикам набор инструментов, позволяющий отслеживать то, как reflow влияет на производительность. В Blink/Webkit браузерах, таких как Chrome, Safari и Opera это можно сделать, открыв вкладку Сеть и записав активность:
Похожая панель Сетьесть и в наборе инструментов Firefox:
В IE данная панель называется UI Responsiveness, вызывается по кнопке F12:
Во всех браузерах reflow и repaint отображается зеленым цветом. Тесты на скриншотах выше всего лишь простой пример, без задействования значительных анимаций во время отрисовки макета. Анимация требует намного больше времени, чем, например, выполнение скрипта. Снизьте количество reflow, и производительность сразу же повысится.
Если у вас получилось повысить производительность вашей анимации или других элементов интерфейса мною предложенными или другими способами, напишите об это в комментариях.
Автор: Craig Buckler
Источник: //www.sitepoint.com/
Редакция: Команда webformyself.