Где еще живет скорость в web

Олег Елифантьев

Где еще живет скорость в Web

MoscowJS #24
Олег Елифантьев, @oelifantiev

Олег Елифантьев

Компания Тензор, г. Ярославль
Веб-разработчик (JavaScript, Node.JS)
Организатор Yaroslavl Frontend Meetup (http://yarfrontend.ru)
@oelifantiev

Давайте нарисуем снежинки?

Менеджер: К новому году на сайт клиента нужны снежинки!

Dev: Okay boss!

Снежинка?

$('.snowflake').each(function(){
   var pos = $(this).offset();
   $(this).css({
      top: pos.top + ...,
      left: pos.left + ...
   });
});
setTimeout(animate, 0);

Нужно больше снежинок!

=(

Может быть проблема в JavaScript?

jQuery тормозит. Расходимся.

=)

Анимировать будем так...

function animate() {
   var col = document.querySelectorAll('.snowflake');
   for (var i = 0; i < col.length; i++) {
      var rect = col[i].getBoundingClientRect();
      col[i].style.top = ..., col[i].style.left = ...
   }
   setTimeout(animate, 0);
}

Что происходит?

Как обычно, нам поможет DevTools

Будем мерять FPS

Маловато!

Почему так получилось?

Запишем все происходящее

223 ms!

Style
recalculation

Style recalculation — вычисление стилей, применяемых к конкретному элементу.

Это дешевая операция.

Layout

HTML

				<body>
					<p>
						Длинный текст
						<img align="right" src="..."/>
						Еще текст
					</p>
				</body>
		

HTML превращается в DOM-дерево


+ body
+--	p
    +--	#text
    +-- img
    +-- #text
		

Есть еще "дерево рендеринга"

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

Есть еще "дерево рендеринга"

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

Layout — пересчет дерева рендеринга на основании стилей элементов и других входных параметров, например размера окна браузера.

Это дорогая операция!

Что приводит к инвалидации дерева рендеринга?

Оптимизации…

Браузер всячески старается оптимизировать процесс. Например, он откладывает применение стилей и пересчет дерева рендеринга до окончания синхронного блока JavaScript. Таким образом он "пакетирует" несколько изменений в один расчет.

Ложка дегтя

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

Это происходит синхронно. Все прочие процессы блокируются! Эффект сравним с работой Garbage Сollector.

Что приводит к "чтению стилей"

Вернемся к нашей анимации

var col = document.querySelectorAll('.snowflake');
for (var i = 0; i < col.length; i++) {
   // read one, forced layout
   var rect = col[i].getBoundingClientRect();
   // write one, layout invalidated
   col[i].style.top = ..., col[i].style.left = ...
}

Как исправить?

Все очень просто!

Нужно сначала все прочитать, а потом все записать!

Спасибо, Кэп!

function animate() {
   var col = document.querySelectorAll('.snowflake');
   [].slice.call(col)
      // read all
      .map(readCoordinates)
         .map(applyAnimationStep)
         // write all
         .forEach(applyToDOM);
   setTimeout(animate, 0);
}

Было

Стало

Paint

Еще раз посмотрим на Timeline

Включаем "Show paint rectangles"

Что делать? Заставим трудиться GPU!

Вместо left и top для перемещения используем CSS Transforms

transform: translate3d(x, y, z);
		

Экономная анимация

requestAnimationFrame!

Используем знание о времени!

function animate(ts) {
   var col = document.querySelectorAll('.snowflake');
   [].slice.call(col)
      .map(calculateNextPosition(ts))
      .forEach(applyToDOM);
   requestAnimationFrame(animate);
}

will-change

CSS-свойство "will-change"

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

.someElt { ... }
.someElt:hover { will-change: transform; }
.someElt:active { transform: rotate(90deg); }
		

will-change ?

Опыт автора: Chrome 43 — эффект сомнительный.

Послесловие

Все это актуально не только для анимации! Любой код, работающий с DOM, может выиграть!

Спасибо за внимание!

Доп. материалы: http://bit.ly/render-speed
Вопросы?

MoscowJS #24
Елифантьев Олег, Тензор
@oelifantiev

Fork me on Github