Как работают браузеры
Пользователи хотят использовать приложения, в которых загрузка контента происходит быстро, а взаимодействие — плавно. Разработчик должен стараться оптимизировать своё приложение как минимум по этим двум показателям.
Чтобы понять, как улучшить производительность и ощущаемую пользователем производительность (User Perceived Performance, UPP), вам необходимо понимать, как работают браузеры.
Обзор
Быстрые приложения дают лучшие ощущения. Пользователи ожидают, что приложение будет грузиться быстро, а взаимодействие с ним будет плавным.
Две главных проблемы в производительности — это проблема скорости сети и проблема однопоточности браузеров.
Сетевые задержки — это главная проблема, которую нужно преодолеть для достижения быстрой загрузки. Чтобы ускорить загрузку разработчик должен посылать запрошенные данные как можно быстрее или, на худой конец, сделать вид, что они отправляются очень быстро. Сетевые задержки — это время, которое требуется для передачи данных от источника к браузеру. Производительность здесь — это то, что делает загрузку страниц как можно более быстрой.
В большинстве своём браузеры рассматриваются как однопоточные приложения. Чтобы достичь плавности взаимодействия, разработчик должен обеспечивать производительность во всём, начиная от плавного скроллинга, до быстрой реакции на нажатие экрана. Время рендера — это ключевое понятие. Разработчик должен обеспечить такую работу приложения, чтобы все его задачи могли быть выполнены достаточно быстро. В таком случае процессор будет свободен для обработки пользовательского ввода. Для решения проблемы однопоточности вы должны понять природу браузеров и научиться разгружать основной поток процесса там, где это возможно и допустимо.
Навигация
Навигация — это первый этап при загрузке приложения. Он происходит каждый раз, когда пользователь запрашивает страницу, вводя URL в адресную строку браузера, нажимает на ссылку, отправляет заполненные поля формы и выполняет некоторые другие действия.
Одна из задач разработчика — сократить время, которое требуется приложению, чтобы этап навигации завершился. В идеальных условиях это обычно не занимает много времени, но задержки сети и ширина канала — препятствия, которые приводят к задержкам загрузки приложения.
DNS запрос
Первый шаг навигации к странице — это поиск места, откуда нужно запрашивать данные. Если вы переходите на https://example.com , браузер грузит HTML-код страницы с IP-адреса 93.184.216.34 . Если вы никогда ранее не были на этом сайте, произойдёт поиск DNS записи.
Ваш браузер запрашивает DNS запись. Как правило, запрос содержит имя сервера, который должен быть преобразован в IP-адрес. Ответ на этот запрос какое-то время будет сохранён в кеше устройства, чтобы его можно было быстро получить при следующем запросе к тому же серверу.
DNS запрос обычно требуется совершить лишь единожды при загрузке страницы. Однако, DNS запросы должны быть выполнены для каждого уникального имени хоста, который запрашивается страницей. Скажем, если ваши шрифты, картинки, скрипты, реклама или счётчики аналитики находятся на разных доменах, DNS запрос будет осуществлён для каждого из них.
Это может быть проблемой с точки зрения производительности, особенно для мобильных сетей. Когда пользователь находится в мобильной сети, каждый DNS запрос должен пройти от мобильного устройства до сотовой вышки, а уже оттуда дойти до авторитетного DNS-сервера. Расстояние и помехи между телефоном, вышкой и сервером имён могут значительно увеличить задержку.
TCP Рукопожатие (Handshake)
В тот момент, когда IP адрес становится известен, браузер начинает установку соединения к серверу с помощью рукопожатия TCP three-way handshake (en-US). Этот механизм спроектирован так, чтобы два устройства, пытающиеся установить связь, могли обменяться параметрами соединения, прежде чем приступать к передаче данных. Чаще всего — через защищённое соединение HTTPS.
Трёхэтапное рукопожатие TCP — это техника, очень часто упоминаемая как «SYN-SYN-ACK» ( SYN, SYN-ACK, ACK , если быть точнее), т.к. при установке соединения передаются 3 сообщения. Это означает, что прежде чем установится соединение, браузер должен обменяться ещё тремя сообщениями с сервером.
TLS Переговоры (Negotiation)
Для установки безопасных соединений с использованием HTTPS требуется ещё одно рукопожатие. На этот раз — TLS переговоры. На этом шаге определяется, какой шифр будет использоваться для шифрования соединения, удостоверяется надёжность сервера и устанавливается безопасное соединение. Этот шаг также требует несколько дополнительных сообщений, которыми должны обменяться сервер и браузер, прежде чем данные будут посланы.

И хотя обеспечение безопасности соединения снижает скорость загрузки приложения, безопасное соединение стоит затрат на него, так как в этом случае данные не могут быть дешифрованы третьим лицом.
После обмена восемью сообщениями, браузер, наконец, достигает всех условий, чтобы сделать запрос.
Ответ на запрос
Как только мы установили соединение с веб-сервером, браузер отправляет инициирующий HTTP GET запрос от имени пользователя. Чаще всего запрашивается HTML файл. В момент, когда сервер получает запрос, он начинает ответ с посылки заголовков ответа и содержимым HTML-файла.
doctype html> html> head> meta charset="UTF-8" /> title>My simple pagetitle> link rel="stylesheet" src="styles.css" /> script src="myscript.js"> script> head> body> h1 class="heading">My Pageh1> p>A paragraph with a a href="https://example.com/about">linka>p> div> img src="myimage.jpg" alt="image description" /> div> script src="anotherscript.js"> script> body> html>
Этот ответ содержит в себе первый байт полученных данных. Время до первого байта (Time to First Byte, TTFB) — это время между моментом когда пользователь отправил запрос, скажем, нажав на ссылку, и моментом получения первого пакета данных HTML. Первый пакет обычно содержит 14КБ данных.
В примере выше ответ значительно меньше, чем 14КБ; скрипты и стили, перечисленные в ответе, не будут запрошены, пока браузер не обработает ответ. Процесс обработки ответа — парсинг — мы обсудим отдельно.
TCP медленный старт / правило 14kb
Объём первого пакета данных — всегда 14KB. Это часть спецификации TCP slow start (en-US) — алгоритма, который балансирует скорость соединения. Такое правило позволяет постепенно, по мере необходимости, увеличивать размеры передаваемых данных, пока не будет определена максимальная ширина канала.
В алгоритме TCP slow start (en-US) каждый следующий отправленный сервером пакет увеличивается в размере в два раза. Например, размер второго пакета будет около 28КБ. Размер пакетов будет увеличиваться до тех пор, пока не достигнет какого-то порогового значения или не упрётся в проблему переполнения.

Если вы когда-то слышали о правиле 14КБ, то должны понимать, что оптимизация производительности загрузки должна учитывать ограничения этого начального запроса. Медленный старт TCP позволяет плавно ускорять передачу данных так, чтобы избежать проблемы переполнения, когда много данных ожидают отправки, но не отправляются из-за ограничений ширины канала.
Контроль переполнения
Любое соединение имеет ограничения, связанные с аппаратной и сетевой системами. Если сервер отправит слишком много пакетов за раз — они могут быть отброшены. Для того, чтобы избежать таких проблем, браузер должен реагировать на получение пакетов и подтверждать, что он получает их. Такой ответ-подтверждение называется Aknowledgements (ACK). Если из-за ограничений соединения браузер не получит данных, то он не пошлёт подтверждений ACK. В этом случае, сервер зарегистрирует, что какие-то пакеты не дошли и пошлёт их заново, что приведёт к лишней работе сервера и дополнительной нагрузке сети.
Парсинг
Как только браузер получает первый кусочек данных, он сразу начинает обрабатывать получаемую информацию. Эта обработка называется «Парсинг» (Parsing). Во время парсинга получаемые данные преобразуются в DOM и CSSOM (en-US), которые напрямую участвуют в отрисовке.
DOM (Объектная модель документа) — это внутреннее представление разметки HTML. Браузер предоставляет доступ к манипуляции объектами этой модели через разные JavaScript API.
Даже если ответ на запрос больше 14КБ, браузер всё равно начинает парсинг данных и пытается отрисовать страницу с теми данными, которые уже доступны. Именно поэтому при оптимизации производительности очень важно включать в инициирующий 14КБ ответ все необходимые для рендера данные — так браузер сможет быстрее начать формирование страницы. Однако, прежде чем что-либо появится на экране, HTML, CSS и JavaScript должны быть обработаны.
Построение дерева объектной модели документа
Мы уже рассказывали о пяти шагах в критическом пути рендеринга.
Первый шаг — это обработка разметки HTML и построение дерева DOM. Обработка HTML включает в себя токенизацию и построение дерева. HTML-токены состоят из тегов старта и финиша, а также атрибутов. Если документ сформирован правильно, его обработка прямолинейна и быстра. Парсер (обработчик) преобразует входящие токены в документ и строит дерево документа.
Когда парсер находит неблокирующие ресурсы (например, изображения), браузер отправляет запрос на загрузку ресурсов, но сам продолжает обработку. Обработка может продолжаться когда обнаружена ссылка на CSS файл, но если обнаружен , особенно если он без параметров async или defer — такой скрипт считается блокирующим и приостанавливает обработку HTML до завершения загрузки скрипта. Несмотря на то, что сканер предзагрузки (о нём ниже) браузера может находить и запрашивать такие скрипты заранее, сложные и объёмные скрипты всё ещё могут стать причиной заметных задержек загрузки страницы.
Сканер предзагрузки
Построение дерева DOM занимает весь поток процесса. Так как это явно узкое место в производительности, был создан особый сканер предзагрузки. Он обрабатывает доступное содержимое документа и запрашивает высокоприоритетные ресурсы (CSS, JavaScript и шрифты). Благодаря этому сканеру нам не нужно ждать, пока парсер дойдёт до конкретного места, где вызывается ресурс. Он запрашивает и получает эти данные заранее, в фоновом режиме, так что когда основной поток HTML-парсера доходит до запроса ресурса, высока вероятность, что ресурс уже запрошен или находится в процессе загрузки. Оптимизации, которые даёт этот сканер, уменьшают время блокирования рендера.
link rel="stylesheet" src="styles.css" /> script src="myscript.js" async> script> img src="myimage.jpg" alt="image description" /> script src="anotherscript.js" async> script>
В примере выше основной поток обрабатывает HTML и CSS. В то же время, сканер предзагрузки находит скрипты и изображение и начинает их загрузку. Чтобы сделать скрипт неблокирующим, добавьте атрибут async или, в случае, если порядок загрузки скриптов важен, атрибут defer .
Примечание: Ожидание получения CSS не блокирует парсинг HTML, но он блокирует JavaScript, потому что JavaScript часто используется для выборки узлов документа по CSS-селекторам.
Построение модели стилей CSSOM
Второй шаг при прохождении критического пути рендеринга — это обработка CSS и построение CSSOM дерева. CSSOM (объектная модель CSS) похожа на DOM. И DOM, и CSSOM — это деревья. Они являются независимыми структурами данных. Браузер преобразует CSS файлы в карту стилей, которую он может понять и с которой может работать. Браузер считывает каждый набор правил в CSS, создаёт дерево узлов с родителями, детьми и соседями, основываясь на CSS селекторах.
Как и в HTML, браузер должен преобразовать полученные правила CSS во что-то, с чем он может работать. Таким образом, весь этот процесс — это повторение формирования DOM, только для CSS.
CSSOM дерево включает в себя стили пользовательского агента — это стили, которые браузер вставляет по умолчанию. Браузер начинает построение модели с наиболее общих правил для каждого узла, постепенно применяя более специфичные правила. Другими словами, он применяет правила каскадно. Отсюда и название CSS — Cascading Style Sheets.
Построение CSSOM происходит очень быстро и не отображается отдельным цветом в средствах разработчика. Оно настолько быстрое, что чаще всего включается в показатель «Повторное вычисление стилей (Recalculate Styles)» в средствах разработчика. Этот показатель показывает общее время обработки стилей — обработку CSS, построение CSSOM и рекурсивное вычисление стилей. С точки зрения оптимизации производительности здесь нечего делать, так как построение CSSOM, в целом, занимает даже меньше времени, чем DNS запрос.
Остальные процессы
Компиляция JavaScript
Как CSS обработан и CSSOM создан, другие ресурсы, например, JavaScript-файлы, продолжают загружаться (спасибо сканеру предзагрузки). JavaScript по окончании загрузки должен быть интерпретирован, скомпилирован, обработан и исполнен. Скрипты преобразовываются в абстрактное синтаксическое дерево (AST). Некоторые браузеры берут Abstract Syntax Tree и передают его в интерпретатор, который преобразует дерево в байт-код. Байт-код исполняется в основном потоке. Весь этот процесс называется компиляцией.
Построение дерева доступности
Браузер также строит дерево доступности, которое используется устройствами-помощниками для понимания и интерпретирования контента. Объектная модель доступности (accessibility object model, AOM) — это семантическая версия DOM. Браузер обновляет AOM в тот же момент, когда обновляется DOM. В то же время, дерево доступности не может быть изменено вспомогательными технологиями.
Пока модель AOM не построена, содержимое страницы недоступно для голосовых помощников и считывателей экрана (en-US) .
Рендеринг
Этапы рендеринга включают в себя стилизацию, компоновку (layout), отрисовку (paint) и, в некоторых случаях, композицию (composition). CSSOM и DOM деревья, созданные на предыдущем этапе комбинируются в дерево рендера, которое затем используется для расчёта положения каждого видимого элемента. После этого элементы будут отрисованы на экране. В некоторых случаях содержимое может быть вынесено на отдельные слои и совмещено (composition) — такой подход увеличивает производительность, позволяя отрисовывать содержимое экрана на графическом процессоре вместо ЦПУ. Это освобождает основной поток.
Стилизация
Третий шаг в критическом пути рендеринга — это комбинирование DOM и CSSOM в дерево рендеринга. Конструирование этого дерева начинается с прохода всего DOM-дерева от корня, с выявлением каждого видимого узла.
Элементы, которые не должны быть показаны, например, , а так же их дети или любые элементы с display:none , такие как script < display: none; >, не будут включены в дерево рендера, так как они не должны быть отрисованы. Узлы с правилом visibility: hidden включены в дерево рендера, так как они всё равно занимают своё место. Так как мы не указали никаких специальных правил для перезаписи стилей агента по умолчанию, узел script в примере выше также не будет включён в дерево рендера.
Каждый видимый узел имеет свои правила из CSSOM. Дерево рендера содержит все видимые узлы с их содержимым и вычисленными стилями. Стили определяются путём применения всех подходящих правил с использованием CSS каскада.
Компоновка (Layout)
Четвёртый шаг на критическом пути рендеринга — это запуск компоновки (layout) элементов дерева рендера. На этом шаге вычисляется геометрия каждого узла, то есть ширина, высота, положение элементов. Reflow (перекомпоновка) — это любой последующий процесс определения размеров и позиции для любой из частей целого документа.
Как только дерево рендера построено — начинается layout. Дерево несёт в себе информацию о том, какие узлы должны быть отрисованы (даже если они невидимы), и какие стили должны быть применены, но в дереве нет никакой информации о размерах и позиции элементов. Чтобы определить эти значения, браузер начинает обход дерева.
На веб-странице практически все элементы прямоугольны (box). Разные устройства и настройки подразумевают бесчисленное количество разных размеров видимой области. На начальной фазе браузер, учитывая размер видимой области, определяет какие размеры разных элементов должны быть на экране. Использует размер видимой области как базис, процесс начинает вычисление с элемента body , затем переходит к его потомкам, вычисляет размеры каждого элемента и резервирует место для тех элементов, размеры которых он ещё не знает (например, изображения).
Момент, когда позиция и размеры узлов вычислены, называется layout. Последующие вычисления позиций и размеров называются reflow. В нашем примере предполагаемый начальный layout происходит перед тем, как изображение получено. Так как мы не задавали размер изображения, в момент получения изображения произойдёт reflow.
Отрисовка (Paint)
Последний шаг критического пути рендеринга — это отрисовка каждого отдельного узла на экране. Момент, когда это происходит впервые, называется first meaningful paint (первая значащая отрисовка). Во время фазы отрисовки или растеризации, браузер конвертирует каждый контейнер box в настоящие пиксели на экране (напомним, что данные контейнеров формируются на этапе layout). Отрисовка подразумевает рисование каждой визуальной частицы элемента на экране (текст, цвета, границы, тени) и рисование заменяемых элементов (картинки, кнопки). Браузер должен выполнять это быстро.
Чтобы обеспечить плавную прокрутку и анимацию, отрисовка каждого элемента занимает весь основной поток. Сюда включается вычисление стилей, повторное вычисление стилей и отрисовка. Все эти этапы должны выполняться не дольше 16.67 мс. (1000мс. / 60 кадров в секунду). При разрешении 2048х1536 экран iPad содержит 3.145.000 пикселей, которые должны быть отрисованы. Это много! Для того, чтобы сделать инициирующую и повторную отрисовки быстрее, можно разбить весь процесс на несколько слоёв. Когда это случается — становится необходима композиция.
Отрисовка может разбить элементы в дереве рендера на слои. Для того, чтобы ускорить их рендер, браузер может перенести отрисовку разных слоёв на GPU (вместо основного потока CPU). Для переноса вычислений отрисовки на GPU вы можете использовать некоторые специальные HTML теги, например и ; а также CSS-свойства opacity , transform и will-change . Узлы, созданные таким образом, будут отрисованы на их собственном слое, вместе с их потомками, если только потомки сами по себе не будут вынесены в отдельные слои.
Слои улучшают производительность. Но, с точки зрения управления памяти, они неэффективны. Поэтому старайтесь не использовать их там, где в них нет необходимости.
Композиция (Compositing)
Когда разделы документа отрисованы на разных слоях, а один слой находится над другим или перекрывает его, становится необходима композиция. Этот шаг позволяет браузеру гарантировать, что каждый слой отрисован на экране в правильном порядке, а содержимое отображается корректно.
При догрузке ранее запрошенных ресурсов (например, изображений) может потребоваться перерассчитать размеры и положение элементов относительно друг друга. Этот перерасчёт — reflow — запускает перерисовку (repaint) и перекомпозицию (re-composite). Если мы заранее определили размер изображения, перерасчёт не будет необходим и в этом случае только тот слой, который должен быть перерисован — будет перерисован. Но если мы не определили размер изображения заранее, то браузер, после получения ответа от сервера, будет вынужден отмотать процесс рендеринга обратно к шагу компоновки (layout) и начать процесс отрисовки ещё раз.
Интерактивность
Можно было бы подумать, что как только основной поток завершает отрисовку страницы — «всё готово». Это не всегда так. Если среди загружаемых ресурсов есть JavaScript, загрузка которого была корректно отложена, а запуск которого происходит только после события onload , основной поток начинает обработку скриптов. Во время этой обработки браузер не может обрабатывать события прокрутки, нажатий и др.
Time to Interactive (TTI, время до интерактивности) — это показатель того, как много времени проходит между самым первым сетевым запросом и моментом, когда страница становится интерактивной. В хронологии этот этап следует сразу за First Contentful Paint. Интерактивностью называется показатель того, что страница отреагировала на действие пользователя за время в 50мс. Если процессор занят обработкой, компиляцией и выполнением JavaScript, то браузер не может отреагировать достаточно быстро, а значит страница считается не интерактивной.
В нашем примере, даже несмотря на то, что изображение загрузилось быстро, скрипт anotherscript.js , размер которого достигает 2МБ, загружается долго. В этом случае пользователь увидит страницу очень быстро, но не будет способен взаимодействовать с ней, пока скрипт не будет загружен, обработан и исполнен. Это плохая практика. Старайтесь избегать полной загрузки процесса.

В примере выше загрузка содержимого DOM заняла около 1.5 секунд. Все это время основной поток процесса был полностью загружен и не был способен обработать пользовательский ввод.
Смотрите также
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
Как браузер рисует страницы
Как обрабатывается HTML, CSS и JS код перед тем, как станет веб-страницей.
Время чтения: 9 мин
Открыть/закрыть навигацию по статье
- Кратко
- Получение ресурсов, Fetching
- Парсинг, Parsing
- DOM
- Render Tree
- Глобальный и инкрементальный Layout
- «Грязные» элементы
- Порядок отрисовки
- Саша Беспоясов советует
- Что такое прогрессивный рендеринг (progressive rendering)?
Обновлено 8 марта 2023
Кратко
Скопировать ссылку «Кратко» Скопировано
Чтобы нарисовать на экране результат работы нашего кода, браузеру нужно выполнить несколько этапов:
- Сперва ему нужно скачать исходники.
- Затем их нужно прочитать и распарсить.
- После этого браузер приступает к рендерингу — отрисовке.
Каждый из процессов очень сложен, и мы не будем рассматривать их до мельчайших подробностей.
Мы лишь обратим внимание на те детали, которые необходимо знать фронтенд-разработчикам, чтобы лучше понимать, почему разные решения по-разному влияют на производительность и скорость отрисовки.
Начнём по порядку.
Получение ресурсов, Fetching
Скопировать ссылку «Получение ресурсов, Fetching» Скопировано
Ресурсы браузер получает с помощью запросов к серверу. В ответ он может получить как, например, данные в виде JSON, так и картинки, видео, файлы стилей и скриптов.
Самый первый запрос к серверу — обычно запрос на получение HTML-страницы (чаще всего index . html ).
В её коде содержатся ссылки на другие ресурсы, которые браузер тоже запросит у сервера:
Document
DOCTYPE html> html lang="en"> head> link href="/style.css" rel="stylesheet"> title>Documenttitle> head> body> img src="/hello.jpg" alt="Привет!"> script src="/index.js"> script> body> html>В примере выше браузер запросит также:
- файл стилей style . css ;
- изображение hello . jpg ;
- и скрипт index . js .
Парсинг, Parsing
Скопировать ссылку «Парсинг, Parsing» Скопировано
По мере того как скачивается HTML-страница, браузер пытается её «прочитать» — распарсить.
DOM
Скопировать ссылку «DOM» Скопировано
Браузер работает не с текстом разметки, а с абстракциями над ним. Одна из таких абстракций, результат парсинга HTML-кода, называется DOM.
DOM (Document Object Model) — абстрактное представление HTML-документа, с помощью которого браузер может получать доступ к его элементам, изменять его структуру и оформление.
DOM — это дерево. Корень этого дерева — это элемент HTML, все остальные элементы — это дочерние узлы.
Для такого документа:
Hello Hello world
html> head> meta charset="utf-8"> title>Hellotitle> head> body> p class="text">Hello worldp> img src="/hello.jpg" alt="Привет!"> body> html>. получится такое дерево:

Пока браузер парсит документ и строит DOM, он натыкается на элементы типа , , , которые содержат ссылки на другие ресурсы.
Если ресурс неблокирующий (например, изображение), браузер запрашивает его параллельно с парсингом оставшейся части документа. Блокирующие ресурсы (например, скрипты) приостанавливают обработку до своей полной загрузки.
Мы можем указывать браузеру, как именно ему следует запрашивать некоторые ресурсы, например, скрипты. Это может быть полезно, когда в скрипте мы собираемся работать с элементами, которые находятся в разметке после тега :
// script.jsconst image = document.getElementById('image')// script.js const image = document.getElementById('image')
body> script src="script.js"> script> img src="/hello.jpg" alt="Hello world" id="image"> body>В этом случае image = = = undefined , потому что браузер успел распарсить только часть документа до этого тега .
А в этом всё в порядке, изображение найдётся:
body> img src="/hello.jpg" alt="Hello world" id="image"> script src="script.js"> script> body>И в этом тоже порядок, атрибут defer скажет браузеру продолжать парсить страницу и выполнить скрипт потом:
body> script src="script.js" defer> script> img src="/hello.jpg" alt="Hello world" id="image"> body>CSSOM
Скопировать ссылку «CSSOM» Скопировано
Когда браузер находит элемент , который указывает на файл стилей, браузер скачивает и парсит его. Результат парсинга CSS-кода — CSSOM.
CSSOM (CSS Object Model) — по аналогии с DOM, представление стилевых правил в виде дерева.
Для документа выше с такими стилями:
body font-size: 1.5rem;> .text color: red;> img max-width: 100%;>body font-size: 1.5rem; > .text color: red; > img max-width: 100%; >. получим такое дерево:

Чтение стилей приостанавливает чтение кода страницы. Поэтому рекомендуется в самом начале отдавать только критичные стили — которые есть на всех страницах и конкретно на этой. Так мы уменьшаем время ожидания, пока «страница загрузится».
Благодаря оптимизациям (например, сканеру предзагрузки) стили могут не блокировать чтение HTML, но они точно блокируют выполнение JavaScript, потому что в JS могут использоваться CSS-селекторы для выборки элементов.
Render Tree
Скопировать ссылку «Render Tree» Скопировано
После того как браузер составил DOM и CSSOM, он объединяет их в общее дерево рендеринга — Render Tree.
Render Tree — это термин, который используется движком WebKit, в других движках он может отличаться. Например, Gecko использует термин Frame Tree.
В итоге для нашего документа выше мы получим такое дерево:

Обратите внимание, что в Render tree попадают только видимые элементы. Если бы у нас был элемент, спрятанный через display : none , он бы в это дерево не попал. Об этом подробнее мы ещё поговорим дальше.
Общая схема парсинга выглядит вот так:

На первых шагах мы разбираемся с HTML и CSS, а затем объединяем их в Render Tree.
Вычисление позиции и размеров, Layout
Скопировать ссылку «Вычисление позиции и размеров, Layout» Скопировано
После того как у браузера появилось дерево рендеринга (Render Tree), он начинает «расставлять» элементы на странице. Этот процесс называется Layout.
Чтобы понимать, где какой элемент должен находиться и как он влияет на расположение других элементов, браузер рассчитывает размеры и положение каждого рекурсивно.
Расчёт начинается от корневого элемента дерева рендеринга, его размеры равны размеру вьюпорта. Далее браузер переходит поочерёдно к каждому из дочерних элементов.
Важно помнить, что Layout построен на поточной модели компоновки. Это значит, что если элементы не влияют на расположение и размеры других элементов, то их положение и размеры можно просчитать за один подход.
Именно поэтому при вёрстке макетов рекомендуется «находиться в потоке» — чтобы браузеру не приходилось несколько раз пересчитывать один и тот же элемент, так страница отрисовывается быстрее.
Глобальный и инкрементальный Layout
Скопировать ссылку «Глобальный и инкрементальный Layout» Скопировано
Глобальный Layout — это процесс просчёта всего дерева полностью, то есть каждого элемента. Инкрементальный — просчитывает только часть.
Глобальный Layout запускается, например, при изменении размера окна, потому что браузеру требуется подогнать всю страницу под новый размер экрана. Это очень дорогой процесс.
Инкрементальный Layout запускает пересчёт только «грязных» элементов.
«Грязные» элементы
Скопировать ссылку ««Грязные» элементы» Скопировано
Это те элементы, которые были изменены, и их дочерние элементы.
Если мы как-то поменяли блок, то браузер перерисует его и его детей, потому что их положение и размеры могут зависеть от родителя.

Дальше браузер приступает к, собственно, отрисовке.
Непосредственно отрисовка, Paint
Скопировать ссылку «Непосредственно отрисовка, Paint» Скопировано
Во время отрисовки (Paint) браузер наполняет пиксели на экране нужными цветами в зависимости от того, что в конкретном месте должно быть нарисовано: текст, изображение, цвет фона, тени, рамки и т. д.
Отрисовка тоже бывает глобальной и инкрементальной. Чтобы понять, какую часть вьюпорта надо перерисовать, браузер делит весь вьюпорт на прямоугольные участки. Логика тут та же, как и в Layout — если изменения ограничены одним участком, то пометится «грязным» и перерисуется лишь он.
Отрисовка — это самый дорогой процесс из всех, что мы уже перечислили.
Порядок отрисовки
Скопировать ссылку «Порядок отрисовки» Скопировано
Порядок отрисовки связан со стековым контекстом.
В общих чертах, отрисовка начинается с заднего плана и постепенно переходит к переднему:
- background — color ;
- background — image ;
- border ;
- children ;
- outline .
CPU и композитинг
Скопировать ссылку «CPU и композитинг» Скопировано
И Layout, и Paint работают за счёт CPU (central process unit), поэтому относительно медленные. Плавные анимации при таком раскладе невероятно дорогие.
Для плавных анимаций в браузерах предусмотрен композитинг (Compositing).
Композитинг — это разделение содержимого страницы на «слои», которые браузер будет перерисовывать. Эти слои друг от друга не зависят, из-за чего изменение элемента в одном слое не затрагивает элементы из других слоёв, и перерисовывать их становится не нужно.
Именно из-за разнесения элементов по разным композиционным слоям свойство transform не так сильно нагружает браузер. Поэтому чтобы анимации не тормозили, их рекомендуется делать с применением transform и opacity .

Применение таких свойств, как, например, transform , «выносит» элемент на отдельный композитный слой, где положение элемента не зависит от других и не влияет на них.
Чтобы узнать, вызывает ли конкретное CSS-свойство композитинг и перерисовку в браузере, воспользуйтесь инструментом «CSS Triggers».
Перерисовка, Reflow (relayout) и Repaint
Скопировать ссылку «Перерисовка, Reflow (relayout) и Repaint» Скопировано
Процесс отрисовки — циклический. Браузер перерисовывает экран каждый раз, когда на странице происходят какие-то изменения.
Если, например, в DOM-дереве добавился новый узел, или изменился текст, то браузер построит новое дерево рендеринга и запустит вычисление позиции и отрисовку заново.
Один цикл обновления — это animation frame.
Зная «расписание отрисовки» браузера, мы можем «предупредить» его, что хотим запустить какую-то анимацию на каждый новый фрейм. Это можно сделать с помощью request Animation Frame ( ) .
const animate = () => // Код анимации>const animate = () => // Код анимации >Эта функция запускает новый кадр анимации: обновляет какое-то свойство или перерисовывает canvas.
Если мы хотим добиться плавной анимации, используя функцию выше, мы должны обеспечить в среднем 60 обновлений экрана за секунду (60 fps — frames per second).
Это можно сделать топорно, через интервал:
// 60 раз в 1000 миллисекунд, приблизительно 16 мс.const intervalMS = 1000 / 60setInterval(animate, intervalMS)// 60 раз в 1000 миллисекунд, приблизительно 16 мс. const intervalMS = 1000 / 60 setInterval(animate, intervalMS)Либо использовать window . request Animation Frame ( ) :
window.requestAnimationFrame(animate)window.requestAnimationFrame(animate)Интервалы не всегда запускаются в нужный момент. set Interval ( ) не учитывает, на какой стадии отрисовки находится страница, и в итоге кадры отрисовки могут быть рваными или дёрганными.
С интервалом анимация может быть рваной, потому что перерисовка может быть запущена в неподходящее время.
А если вкладка была неактивна, то интервал может «попытаться догнать время», и несколько кадров запустятся разом:

С request Animation Frame ( ) анимация плавнее, потому что браузер знает, что в следующем фрейме надо запустить новый кадр анимации.
Она не гарантирует, что анимация будет запущена строго раз в 16 мс, но значение будет достаточно близким.

На практике
Скопировать ссылку «На практике» Скопировано
Саша Беспоясов советует
Скопировать ссылку «Саша Беспоясов советует» Скопировано
Для динамики всегда используйте transform и opacity , избегайте изменения остальных свойств (типа left , top , margin , background и так далее).
Таким образом вы дадите браузеру возможность оптимизировать отрисовку, отчего страница станет отзывчивее.
Для анимаций, которые необходимо перерисовывать на каждый фрейм, используйте request Animation Frame ( ) .
Это сделает тяжёлую анимацию менее рваной.
На собеседовании
Скопировать ссылку «На собеседовании» Скопировано
Что такое прогрессивный рендеринг (progressive rendering)?
Скопировать ссылку «Что такое прогрессивный рендеринг (progressive rendering)?» Скопировано
Скопировать ссылку «Марина Дорошук отвечает» Скопировано
Чтобы понять что такое progressive rendering, нужно понимать отличие client-side rendering от server-side rendering.
При client-side rendering (CSR) контент отрисовывается на стороне клиента (в браузере). Такой подход используется в React, когда браузеру отсылается практически пустой HTML-документ, а потом запускается скрипт, который генерирует HTML в указанном скрипту теге. Как правило это . Пользователь будет видеть пустую страницу, пока JS-файл полностью не загрузится.
При server-side rendering (SSR) HTML-разметка генерируется на сервере, отсылается браузеру и после этого отрисовывается на клиенте. Пользователь увидит контент сразу же, но не сможет взаимодействовать со страницей, пока не загрузится JS-файл.
При использовании прогрессивного рендеринга, кусочки HTML генерируется на сервере и отсылаются браузеру в порядке их приоритетности. То есть, элементы с самым высоким приоритетом (например , фон, главная интерактивная часть страницы) генерируются на сервере, отсылаются браузеру и отрисовываются в первую очередь. Это позволяет пользователю увидеть самый важный контент как можно скорее, не дожидаясь полной загрузки всего контента. То есть, progressive rendering что-то среднее между client-side rendering и server-side rendering.
Техники реализации прогрессивного рендеринга:
- Ленивая загрузка (Lazy Loading). Загрузка контента по мере необходимости. Например, если страница достаточно большая, не нужно загружать изображения вне вьюпорта. Загрузка изображения стартует за некоторое время до того как она появится во вьюпорте. Эту же технику можно использовать для загрузки контента изначально скрытых элементов. Например, можно загрузить контент закрытого меню когда пользователь наводит курсор на кнопку открытия.
- Приоритизация контента. Например, не загружать изначально все CSS-стили. Добавлять в загрузку только тех стилей, которые нужны для текущей видимой области HTML-документа. Остальные стили можно добавить в .
ivanov-v / web.md
Протокол передачи данных, используемый обычно для получения информации с веб-сайтов. Использует он «клиент-серверную» модель. То есть существуют клиенты, которые формируют и отправляют запрос. И серверы, которые слушают запросы и, соответственно, на них отвечают.
В качестве клиентов выступают известные многим веб-браузеры. А в качестве серверного ПО используют: Apache, IIS, nginx и т.д.
HTTPS (HyperText Transfer Protocol Secure)
Это расширение протокола HTTP, которое поддерживает криптографические протоколы и передает информацию не в открытом виде, а в зашифрованном.
DNS (Domain Name System)
Система доменных имен. Если говорить в целом, то она хранит информацию о доменах. Например, какому IP адресу соответствует определенное имя. Приведу пример: когда вы открываете свой любимый сайт, то обращаетесь к нему по имени. Но в поля Source Address и Destination Address, которые работают на сетевом уровне (это тема следующей статьи, но я немного забегу вперед), нельзя вставить имя. Там обязательно должен присутствовать именно IP адрес. Вот DNS как раз этим и занимается. Она сообщает, какой IP адрес у запрошенного имени. Вы, к примеру, обращаетесь на google.ru. Ваш компьютер понятия не имеет, кто и что это. Он спрашивает у DNS-сервера: Кто такой google.ru? И сервер отвечает, что google.ru — это 74.125.232.239 (это один из его адресов). И уже после этого, компьютер отправляет запрос на 74.125.232.239. Для пользователя все останется по-прежнему, и в адресной строке он также будет видеть google.ru.
SSH (Secure Shell)
В переводе с английского — безопасная оболочка. Как и Telnet позволяет управлять ОС. Отличие его в том, что он шифрует весь трафик и передаваемые пароли. Шифруется при помощи алгоритма Диффи-Хеллмана. Практически все современные ОС системы умеют работать с этим протоколом. Самое главное отличие SSH от Telnet — это то, что SSH шифрует трафик, а Telnet нет.
CORS (Cross-Origin Resource Sharing)
Отправлять Аякс-запросы к серверам с другим доменом запрещено на уровне браузера. Чтобы это было возможно, появился стандарт CORS – кросс-доменные запросы. Идея проста – пусть клиент шлет Аякс-запрос к чужому серверу. Браузер добавит в запрос особые заголовки с информацией о том, что запрос с другого домена. На их основании сервер решит, как обрабатывать такой запрос, и добавит особые заголовки в ответ. Стандарт CORS различает простые и сложные запросы. Простым считается запрос методами:
Сервер, получив на обработку подобный запрос, должен прочесть Origin и решить, как его обрабатывать. Заголовок ответа Access-Control-Allow-Origin регулирует, с какого домена разрешено запрашивать данные. Это может быть как веб-адрес, так и знак астерикса (звездочки), если разрешено всем. Несколько разных адресов через запятую, к сожалению, не поддерживаются.
Обратите внимание на такую вещь: мы намерены использовать CORS, чтобы дергать чужие API. С вероятностью почти 100% они работают по протоколу JSON, то есть принимают и отдают заголовок Content-Type: application/json. Вроде бы мелочь, но такой запрос автоматом перестает быть простым и переходит в разряд сложных, где схема взаимодействия иная.
Сложные запросы проходят в два этапа. Сначала браузер делает запрос по тому же урлу, но методом OPTIONS (официально называется preflight request). Сервер должен ответить: какими другими методами и дополнительными заголовками (помимо стандартных) можно обращаться к этому урлу. И только получив разрешение, браузер сделает запрос на основной урл.
От адресной строки до рендера
- Пользователь ввел адрес
- Браузер проверяет наличие домена по DNS
- Устанавливается TCP соединение
- Происходит рукопожатие между клиентом и сервером
- Отправляется HTTP — запрос на сервер
- Сервер отвечает клиенту документом с кодом 200/другой код
- Браузер парсит html (разбор разметки в специальное дерево)
- На выходе получается дерево DOM-элементов с корнем document
- После парсинга делает запросы за изображениями, стилями, скриптами, favicon и т.д.
- Во время разбора браузер парсит CSS-файлы, содержимое тегов и атрибутов «style» c помощью «лексической и синтаксической грамматики CSS».
- Каждый CSS-файл разбирается в объект StyleSheet, каждый из таких объектов содержит правила CSS с селекторами и объектами в соответствии с грамматикой CSS.
- Путём перебора DOM-узлов и вычисления для каждого узла значений CSS-стилей создаётся «Дерево рендера» (Render Tree или Frame Tree)
- Вычисляются координаты и размеры элементов. Создаются слои для описания того, какие части страницы можно анимировать без необходимости повторного растрирования. Каждый объект (фрейма или рендера) присваивается слою.
- Во время процесса рендеринга уровни графических вычислений могут использовать процессор компьютера или графический процессор (GPU).
- Из полученного от сервера HTML-документа формируется DOM (Document Object Model).
- Загружаются и распознаются стили, формируется CSSOM (CSS Object Model).
- На основе DOM и CSSOM формируется дерево рендеринга, или render tree — набор объектов рендеринга (Webkit использует термин «renderer», или «render object», а Gecko — «frame»). Render tree дублирует структуру DOM, но сюда не попадают невидимые элементы (например — , или элементы со стилем display:none;). Также, каждая строка текста представлена в дереве рендеринга как отдельный renderer. Каждый объект рендеринга содержит соответствующий ему объект DOM (или блок текста), и рассчитанный для этого объекта стиль. Проще говоря, render tree описывает визуальное представление DOM.
- Для каждого элемента render tree рассчитывается положение на странице — происходит layout. Браузеры используют поточный метод (flow), при котором в большинстве случаев достаточно одного прохода для размещения всех элементов (для таблиц проходов требуется больше).
- Наконец, происходит отрисовка всего этого добра в браузере — painting.
В процессе взаимодействия пользователя со страницей, а также выполнения скриптов, она меняется, что требует повторного выполнения некоторых из вышеперечисленных операций.
В случае изменения стилей элемента, не влияющих на его размеры и положение на странице (например, background-color, border-color, visibility), браузер просто отрисовывает его заново, с учётом нового стиля — происходит repaint (или restyle).
Если же изменения затрагивают содержимое, структуру документа, положение элементов — происходит reflow (или relayout). Причинами таких изменений обычно являются:
- Манипуляции с DOM (добавление, удаление, изменение, перестановка элементов);
- Изменение содержимого, в т.ч. текста в полях форм;
- Расчёт или изменение CSS-свойств;
- Добавление, удаление таблиц стилей;
- Манипуляции с атрибутом «class»;
- Манипуляции с окном браузера — изменения размеров, прокрутка;
- Активация псевдо-классов (например, :hover).
Это легковесный, интерпретируемый или JIT-компилируемый, объектно-ориентированный язык с функциями первого класса. Наиболее широкое применение находит как язык сценариев веб-страниц, но также используется и в других программных продуктах, например, node.js или Apache CouchDB. JavaScript это прототипно-ориентированный, мультипарадигменный язык с динамической типизацией, который поддерживает объектно-ориентированный, императивный и декларативный (например, функциональное программирование) стили программирования.
У js отсутствует стадия компиляции, как у языков, подобных Java или C. Код непосредственно передается в интерпретатор, который его выполняет по мере чтения. Поэтому любая программа на js состоит из текстовых файлов.
В отличие от статических языков, проверка типов в js происходит в момент выполнения кода. А значит, если мы случайно ошиблись и, например, вызываем несуществующую функцию, то ошибка всплывет только во время вызова этой функции. В статических языках подобные ошибки отслеживаются на этапе анализа исходного текста без запуска кода на выполнение. Такие инструменты называются статическими анализаторами кода. Слово статический как раз означает, что программа не выполняется. Для динамических языков они тоже существуют, но отслеживают значительно меньшее число ошибок.
Эту черту языка нельзя назвать положительной. Слабая типизация означает, что js автоматически приводит типы там, где считает нужным. Подробнее этот механизм описан в спецификации. С одной стороны, слабая типизация позволяет писать более короткий код. С другой стороны, количество ошибок резко возрастает, так как вместо остановки выполнения, код продолжает работать там, где он не должен работать. Например, мы можем сложить число со строкой по ошибке, и js молча выполнит эту операцию. В языках типа ruby/python подобное невозможно. Выполнение кода прекратится с сообщением о невозможности выполнить операцию для этих типов. Никогда не полагайтесь на эту особенность языка, во избежание проблем.
EventLoop (параллельная модель и цикл событий)
Параллелизм в JavaScript основывается на модели «событийного цикла». Эта модель отличается от модели других языков, например C или Java.
Модель событийного цикла (event loop) называется так потому, что отслеживает новые события в цикле.
Каждая задача выполняется полностью, прежде чем начнет обрабатываться следующая. Благодаря этому мы точно знаем: когда выполняется текущая функция – она не может быть приостановлена и будет целиком завершена до начала выполнения другого кода (который может изменить данные, с которыми работает текущая функция). Это отличает JavaScript от такого языка программирования как C. Поскольку в С функция, запущенная в отдельном потоке, в любой момент может быть остановлена, чтобы выполнить какой-то другой код в другом потоке.
У данного подхода есть и минусы. Если задача занимает слишком много времени, то веб-приложение не может обрабатывать действия пользователя в это время (например, скролл или клик). Браузер старается смягчить проблему и выводит сообщение «скрипт выполняется слишком долго» («a script is taking too long to run») и предлагает остановить его. Хорошей практикой является создание задач, которые исполняются быстро, и если возможно, разбиение одной задачи на несколько мелких.
Web Worker или кросс-доменный фрейм имеют свой собственный стек, кучу и очередь событий. Два отдельных событийных потока могут связываться друг с другом, только через отправку сообщений с помощью метода postMessage. Этот метод добавляет сообщение в очередь другого, если он конечно принимает их.
Очень интересное свойство цикла событий в JavaScript, что в отличие от множества других языков, поток выполнения никогда не блокируется. Обработка I/O обычно осуществляется с помощью событий и функций обратного вызова, поэтому даже когда приложение ожидает запрос от IndexedDB или ответ от XHR, оно может обрабатывать другие процессы, например пользовательский ввод.
Существуют хорошо известные исключения как alert или синхронный XHR, но считается хорошей практикой избегать их использования.
В JavaScript есть 7 основных типов.
- number для любых чисел: целочисленных или чисел с плавающей точкой.
- string для строк. Строка может содержать один или больше символов, нет отдельного символьного типа.
- boolean для true/false.
- null для неизвестных значений – отдельный тип, имеющий одно значение null.
- undefined для неприсвоенных значений – отдельный тип, имеющий одно значение undefined.
- object для более сложных структур данных.
- symbol для уникальных идентификаторов.
Оператор typeof позволяет нам увидеть, какой тип данных сохранён в переменной. Имеет две формы: typeof x или typeof(x). Возвращает строку с именем типа. Например, «string». Для null возвращается «object» – это ошибка в языке, на самом деле это не объект.
Основной концепцией управления памятью в JavaScript является принцип достижимости. Если упростить, то «достижимые» значения – это те, которые доступны или используются. Они гарантированно находятся в памяти.
- Сборка мусора выполняется автоматически. Мы не можем ускорить или предотвратить её.
- Объекты сохраняются в памяти, пока они достижимы.
- Наличие ссылки не гарантирует, что объект достижим (от корня): несколько взаимосвязанных объектов могут стать недостижимыми как единое целое.
Рендеринг веб сайтов 101
Вы вводите название сайта в адресную строку браузера, нажимаете enter, и по привычке видите запрашиваемую страницу. Все просто: ввел название сайта — сайт отобразился. Однако для более любознательных хочу рассказать, что происходит между тем как браузер начинает получать куски сайта (да, сайт приходит кусками, по-другому — чанками) и отображает полностью нарисованную страницу.
Как устроен браузер?
Перед историей о том, как браузер рисует страницу, важно понять как он устроен, какие процессы и на каком уровне выполняются. При знакомстве с процессом рендеринга мы не раз вспомним о компонентах браузера. Итак, под капотом браузер выглядит примерно следующим образом:

User Interface — это все что видит пользователь: адресная строка, кнопки вперед/назад, меню, закладки — за исключением области где отображается сайт.
Browser Engine отвечает за взаимодействие между User Interface и Rendering Engine. Например клик по кнопке назад должен сказать компоненте RE что нужно отрисовать предыдущее состояние.
Rendering Engine отвечает за отображение веб-страницы. В зависимости от типа файла, эта компонента может парсить и рендерить как HTML/XML и CSS, так и PDF .
Network выполняет xhr запросы за ресурсами, и в целом, общение браузера с остальным интернетом происходит через эту компоненту, включая проксирование, кэширование и так далее.
JS Engine место где парсится и исполняется js код.
UI Backend используется чтобы рисовать стандартные компоненты типа чекбоксов, инпутов, кнопок.
Data Persistence отвечает за хранение локальных данных, например в куках, SessionStorage, indexDB и так далее.
Далее узнаем как рассмотренные компоненты браузера взаимодействуют между собой и разберем подробнее, что происходит внутри Rendering Engine. Другими словами …
Как браузер переводит html в пиксели на экране?
Итак, с помощью компонента Network браузер начал получать html-файл чанками обычно по 8кб, что дальше? А далее идет процесс парсинга (спецификация процесса) и рендеринга этого файла в компоненте, как вы уже догадались — Rendering Engine.
Важно! Для повышения юзабилити, браузер не дожидается пока загрузится и распарсится весь html. Вместо этого браузер сразу пытается отобразить пользователю страницу (далее рассмотрим как).
Сам процесс парсинга выглядит так:

Результатом парсинга является DOM дерево. Возьмем к примеру такой html:
Web Rendering Hey
Lorem ipsum.
DOM дерево такого html файла будет выглядеть так:

По мере того как браузер парсит html файл, он встречает теги содержащие ссылки на сторонние ресурсы ( , ,
и так далее) — по мере их обнаружения происходит запрос за этими ресурсами.
Таким образом, отправив запрос по адресу прописанному в атрибуте href тега и получив файл css стилей, браузер парсит этот файл и строит так называемый CSS Object Model — CSSOM.
Представим что у нас есть такой файл стилей:
body < font-size: 14px; >.wrapper < width: 960px; margin: 0 auto; >.wrapper .header h1 < font-size: 26px; >.wrapper p < color: red; >footer
Из которого получим такой CSSOM:

Attention: тут построено дерево из стилей нашего css-файла. Кроме того, также есть user agent’s styles — дефолтные стили браузера и инлайновые стили — прописанные в html тегах.
Подробнее об алгоритме парсинга css стилей можно прочитать в спецификации.
Теперь у нас есть DOM и CSSOM — первый отвечает на вопрос «что?», второй на вопрос «как?». Если думаете, что следующим этапом является соединение DOM и CSSOM’а, то вы совершенно правы! DOM + CSSOM = Render Tree.
Render Tree — это дерево видимых (!) элементов построенных в том порядке, в котором они должны рендериться на странице. Обратите внимание, что элементы имеющие css правило display: none или другие, отрицательно влияющие на отображение — не будут находится в render tree.
Браузер строит Render Tree чтобы точно определить что ему нужно отрисовать и в каком порядке. Построение Render дерева происходит примерно так: начиная с рутового элемента (html), парсер проходит по всем видимым элементам (пропуская link, script, meta, скрытые через css элементы) и для каждого видимого элемента находит соответствующее css правило из CSSOM.
В движке firefox’a элементы Render Tree называются фреймами (frames). Webkit использует термин renderer или render object. Render object знает как разместить себя на странице, а так же содержит информацию о своих дочерних элементах. И для самых любознательных, если заглянуть в исходники webkit’a — можно найти класс который так и называется — RenderObject.
Продолжая наш пример мы получим такой Render Tree:

На данный момент мы имеем в некотором состоянии Render Tree — дерево содержащее информацию о том что и как нужно отрисовать. Теперь браузер должен понять на каком месте и с какими размерами будет отображаться элемент. Процесс вычисления позиции и размеров называется Layout.
Layout — это рекурсивный процесс определения положения и размеров элементов из Render Tree. Он начинается от корневого Render Object, которым является , и проходит рекурсивно вниз по части или всей иерархии дерева высчитывая геометрические размеры дочерних render object’ов. Корневой элемент имеет позицию (0,0) и его размеры равны размерам видимой части окна, то есть размеру viewport’a.
В Html используется поточная модель компоновки (flow based layout), другими словами геометрические размеры элементов в некоторых случаях можно рассчитать за один проход (если элементы, встречающиеся в потоке позже, не влияют на позицию и размеры уже пройденных элементов).
Layout может быть глобальный, когда требуется рассчитать положение render object’ов всего дерева, и инкрементальный, когда требуется рассчитать только часть дерева. Глобальный layout происходит, например, при изменении размеров шрифта или при событии resize’a. Инкрементальный layout происходит только для render object’ов, помеченных как «dirty».
Пара слов о «системе грязных битов (dirty bit system)». Эта система используется браузерами для оптимизации процесса, чтобы не пересчитывать весь layout. При добавлении нового или изменении существующего render object — он сам и его дочерние элементы помечаются флагом «dirty». Если render object не изменяется, но его дочерние элементы были изменены или добавлены, то этот render object помечается как «children are dirty».
К концу процесса layout каждый render object имеет свое положение и размеры.
Подводя промежуточный итог: браузер знает что, как и где рисовать. Следовательно — осталось только нарисовать. Этот процесс, как ни странно, называется Paint.
Paint — этап, где пиксель монитора заполняется цветом указанным в свойствах render object’а и белый экран превращается в картину задуманную автором (разработчиком). На всем пути рендеринга — это самый дорогой процесс (не то чтобы предыдущее дешевые).
Также, как и процесс layout, отрисовка (paint) может быть глобальной — дерево перерисовывается полностью, и инкрементальной — дерево перерисовывается частично. Для частичного перерисовывания render object помечает свой rectangle как невалидный. Операционная система расценивает эту область как требующую перерисовки и вызывает событие paint. При этом браузер умеет объединять области, чтобы выполнить разом перерисовку для всех мест, где это необходимо.
Определение размеров и положения элементов дерева (layout) и перерисовка (paint) являются дорогостоящими процессами. Они выполняются на уровне CPU. Разрабатывая динамические веб приложения, в которых эти процессы будут запускаться очень часто — мы никогда не достигнем плавных анимаций.
Значит, должно быть что-то, что помогло бы создавать сайты с богатой анимацией, при этом не нагружая CPU и рисуя каждый кадр менее чем за 16,6мс (60 fps). Действительно, браузер выполняет еще один этап, который помогает оптимизировать динамику сайтов — Composite (композиция).
Перед композицией, все нарисованные элементы находятся на одном слое (memory layer). То есть, изменение параметров (например, геометрических размеров или положения) одних элементов повлекут перерасчет параметров соседних элементов. Но если распределить элементы на композиционные слои — изменение параметров элемента вызовут перерасчет только на определенном слое, не затрагивая при этом элементы на других слоях. Таким образом, этот процесс является самым дешевым по производительности, поэтому нужно стараться вносить изменения вызывающие только composite.
Резюмируя вышесказанное, получаем такой процесс рендеринга веб страницы:

TLDR;
Браузер получает html файл, парсит его и строит DOM. Встречая css стили, браузер их подгружает, парсит, строит CSSOM и объединяет вместе с DOM’ом — получаем Render Tree. Осталось выяснить где расположить элементы из Render Tree — этим занимается задача layout. После расположения элементов, можно начать рисовать их — это задача paint, этап на котором заполняются пиксели экрана.Динамика
Что происходит когда изменяется css свойство? Или, например, добавляется новый dom узел? В случае изменения css свойств все зависит от изменяемого свойства. Есть только два свойства которые вызывают задачу composite — это opacity и transform. Только эти два свойства являются самыми дешевыми для анимации. К примеру, изменение background вызовет задачу paint (затем composite), а изменение display вызовет сначала layout, далее paint, после чего composite. Список задач, которые вызываются изменениями стилей можно посмотреть на csstriggers.com.
При добавлении новой ноды в dom дерево — очевидно браузеру нужно добавить новый объект в дерево, посчитать его положение на странице, посчитать положения других элементов на странице (если они были аффектнуты новым элементом), и в конце все это нарисовать — звучит дорого. Поэтому делая такие операции необходимо иметь в виду производительность, ведь не каждый пользователь интернета запускает ваше веб-приложение на самой последней модели устройства.
Подводя итог, мы рассмотрели из каких компонентов состоит браузер, как они взаимодействуют друг с другом и как Rendering Engine рисует страницу пользователю.
Посмотреть вышеописанное можно в devtools’ах хрома, но чтобы не выходить за рамки названия статьи — на этом пока все.
Источники
- https://blog.algolia.com/performant-web-animations/
- https://www.zeolearn.com/magazine/components-of-web-browsers
- https://www.html5rocks.com/ru/tutorials/internals/howbrowserswork
- https://blog.sessionstack.com/how-javascript-works-the-rendering-engine-and-tips-to-optimize-its-
performance-7b95553baeda - https://developers.google.com/web/fundamentals/performance/rendering
