Оптимизация рендеринга веб-страницы

imageИз-за давления бизнеса, мы стремимся сделать всё быстрее. От этого страдает планирование и многие вещи не учитываются. Например, легко забыть о производительности и через какое-то время столкнуться с тем, что на более слабых машинах и планшетах обилие движущихся элементов страшно тормозит и дёргается в конвульсиях. Посмотрим, что можно сделать если вы столкнулись с такой проблемой или хотели бы её избежать.Начнём издалека, с той штуки с которой вы всё это читаете. Большинство экранов обновляют изображение примерно 60 раз в секунду и это создаёт иллюзию движения. При каждом обновлении изображение незначительно меняется, поэтому нам кажется что все преобразования происходят плавно. По сути речь идёт о кадрах, как в киноплёнке.

Если изображение (в нашем случае веб-страница) не успевает отрисоваться, мы можем различить что оно дёргается, плавность пропала, и мы пропускаем кадры. Чтобы показать 60 кадров в течении 1 секунды (1000 мс) нам необходимо показывать новый кадр примерно за 16,6 мс. Иначе говоря, если мы видим скачки движения — новое изображение не успевает отрисоваться за 16 миллисекунд.

Чтобы определить проблему, нам надо рассмотреть этапы того как браузер создаёт страницу и понять чем занят процессор вместо полезной и нужной деятельности. Подробно весь процесс сборки страницы описан в работе Тали Гарсиель «Как работает браузер». Если упрощать, то при загрузке страницы браузер разбирает html и css на узлы, формирует из них деревья, объединяет их, и рассчитывает то, как должен выглядеть каждый узел.

Далее происходят два процесса:

Layout — рассчитывается положение и размер элементов. Paint — применяются стили и непосредственно отрисовываются элементы. Когда вы изменяете что-то на странице, браузер должен снова рассчитать и обновить изменившиеся элементы. Само по себе перерисовывание элементов дорогостоящая в процессорном времени операция и её нужно избегать без лишней необходимости.

Мы можем убедиться в этом заглянув в Chrome Dev Tools и воспользовавшись Timeline (на май 2015 он выглядит примерно так).

image

Timeline позволяет записать разные активности браузера во время взаимодействия с ним и оценить сколько они занимают процессорного времени. Если речь идёт о рендеринге, то можно оценить сколько стоит процессорного времени перерисовать тот или иной элемент. Не все они при перерисовке имеют одинаковую стоимость. Какие-то более дорогостоящие и сложные, другие менее. Определяется это эмпирическим путём, записывая и изучая таймлайн.

В Dev Tools также есть опция Show paint rectangles, которая позволяет определить происходит ли перерисовка в данный момент. Элемент, который перерисовывается, подсвечивается. Включается эта опция во вкладке Rendering.

image

Рассмотрим простой пример. Допустим у нас есть некий блок, который мы анимировали достаточно простым способом — сменой свойства left у абсолютно позиционированного элемента.

.elem{ width: 200 px; height: 100 px; background-color: lightgray; color: white; position: absolute; left: 100 px; top: 100 px; transition: 1s ease; } .elem--active{ left: 400 px; } Если мы рассмотрим это действие с включенной выше опцией, мы увидим что в процессе движения перерисовка идёт постоянно. По факту в этом блоке ничего не меняется и его не нужно перерисовывать. Следовательно, это ненужные действия от которых надо избавиться чтобы уложиться в 16 миллисекунд.

image

Как мы можем это сделать? Если посмотреть спецификацию (http://www.w3.org/TR/css3-transforms/#transform-property), то там в описании свойства translate написано, что если у свойства есть значение отличное от пустого, то это создаст отдельный контекст, и элемент будет выведен в новый слой.

Исторически, браузеры объединяли всю веб-станицу в один слой, однако со временем возникла потребность выделять отдельно некоторые элементы и управлять ими при помощи графического процессора (GPU). Это значительно сокращает нагрузку на центральный процессор.

Перепишем наш CSS для использования с transform: translateX ().

.elem--active{ transform: translateX (400 px); } А теперь и попробуем вызвать тоже самое взаимодействие.

image

Область перестала постоянно перерисовываться, и был создан отдельный слой. Анимация стала более плавной за счёт того что transform использует субпиксельную точность (техника обработки изображения для улучшения качества его отображения). Свойство left привязано к пиксельной сетке и движения в первом варианте были более «дёрганными».

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

Обратите внимание, перерисовка всё ещё происходит, причём дважды — в начале анимации и в конце. Допустим, это нас не устраивает, и мы хотим совсем избавиться от неё. Нам надо понять почему она возникает.

У хрома есть свойство show composite layers. Оно подсвечивает границы слоёв и позволяет увидеть какой элемент страницы вынесен в отдельный слой. Включим его, и вернёмся к нашему примеру.

image

Запустим анимацию.

image

Как мы можем видеть по оранжевой границе — блок был выделен в отдельный слой и передвинут. После чего границы растворились. Не трудно догадаться что произошло. Блок был вырван из общего слоя страницы (первый paint), слой с ним был передвинут, после чего снова была вызван paint, чтобы объединить слои.

Чтобы избавиться от двух лишних перерисовок, нам необходимо выделить блок в отдельный слой и предотвратить его слияния с другими. Это можно сделать при помощи transform: translate3d, заменив двухмерное преобразование на трёхмерное.

.elem{ … translate: transform3d (0,0,0); … } .elem--active{ translate: transform3d (400 px,0,0); } image

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

Производительность существенно улучшилась. Вот как выглядит Timeline при использовании свойства left:

image

А вот при использовании transform:

image

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

У данного подхода, тем не менее, есть отрицательная сторона. Во-первых, 3d технологии в браузере всё ещё экспериментальная технология и до стабильности ей ещё далеко. Например, transform3d не поддерживается полностью в IE вплоть до последних версии. Поэтому, этот приём не годится, если у вас значительная часть пользователей пользуются этим браузером. Остаётся полагаться на transformX/Y у которого значительно большая поддержка.

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

Суммируя всё описанное выше:

Лишние и ненужные paint«ы плохо сказываются на производительности Диагностировать наличие неоптимальных участков позволяют встроенные в хром инструменты разработчика Избавить от лишних перерисовок можно при помощи translate: transform«ов При оптимизации необходимо учитывать поддержку браузеров и пользователей мобильных устройств

© Habrahabr.ru