Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як працює браузер під час введення запиту та етапів рендерингу». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Пайплайн рендерингу браузера** - повна послідовність від введення URL до пікселів на екрані. 1. DNS lookup (кеш → hosts → рекурсивний DNS-сервер) 2. TCP handshake (SYN, SYN-ACK, ACK) 3. TLS handshake (тільки HTTPS) 4. HTTP GET запит і відповідь сервера 5. Парсинг HTML → DOM, CSS → CSSOM, виконання JS 6. Render tree (DOM + CSSOM, приховані елементи виключені) 7. Layout (позиції та розміри), Paint (пікселі), Composite (шари GPU) **Головне:** JS без `async`/`defer` блокує парсинг. Анімації через `transform` працюють на compositor thread і не запускають layout або paint.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Пайплайн рендерингу браузера** - послідовність кроків від введення URL до пікселів на екрані, поділена на дві фази: мережева та рендеринг. ## Теорія ### TL;DR - Мережева фаза: DNS lookup, TCP handshake, TLS (тільки HTTPS), HTTP-запит, відповідь сервера - Фаза рендерингу: парсинг HTML (DOM), парсинг CSS (CSSOM), виконання JS, render tree, layout, paint, composite - JS без `async` або `defer` повністю блокує парсинг HTML - Анімація через `transform` минає layout і paint - compositor обробляє її на GPU - Читання layout-властивості одразу після запису в стиль примушує синхронний layout - у циклах це вбиває продуктивність ### Огляд пайплайну ```javascript // Від натискання Enter до пікселів - кожен крок по порядку: // 1. DNS: example.com → 93.184.216.34 // 2. TCP: SYN → SYN-ACK → ACK // 3. TLS: сертифікат + узгодження шифру (тільки HTTPS) // 4. HTTP: GET /index.html HTTP/1.1 // 5. Відповідь: сервер надсилає байти HTML (chunked або повністю) // 6. DOM: парсер будує дерево вузлів з HTML інкрементально // 7. CSSOM: парсер будує дерево стилів з CSS (потрібне повністю) // 8. JS: виконується, може змінювати DOM або CSSOM // 9. Render: DOM + CSSOM = render tree (тільки видимі вузли) // 10. Layout: обчислює точну позицію та розмір кожного вузла // 11. Paint: растеризує пікселі по шарах (від нижнього до верхнього) // 12. Composite: GPU об'єднує шари → кадр на екрані ``` Кожен крок виробляє результат, потрібний наступному. Якщо один зупинився - все після нього чекає. ### DNS resolution Перед тим як відкрити з'єднання, браузеру потрібна IP-адреса. Він перевіряє чотири місця по черзі: 1. Власний DNS-кеш браузера (TTL зазвичай короткий) 2. Файл `hosts` операційної системи (статичні записи, зручні для локальної розробки) 3. Кеш резолвера ОС 4. Рекурсивний DNS-сервер - провайдерський або публічний, наприклад 8.8.8.8 Рекурсивний сервер, якщо не має кешу, проходить ієрархію DNS: кореневі сервери → TLD-сервер (`.com`, `.io`) → авторитетний сервер домену. На прогрітому кеші цей крок займає менше 1мс. На холодному запиті додай 20-120мс залежно від географії. ### TCP та TLS handshake TCP потребує три повідомлення перед тим, як передавати дані: - **SYN**: клієнт відкриває з'єднання - **SYN-ACK**: сервер підтверджує - **ACK**: клієнт підтверджує, з'єднання готове Для HTTPS одразу після цього йде TLS handshake (рукостискання): сервер надсилає сертифікат, обидві сторони домовляються про шифр і виводять сесійні ключі. HTTP/2 мультиплексує всі запити через одне TCP-з'єднання, прибираючи витрати на handshake для кожного ресурсу. HTTP/3 використовує QUIC і поєднує TCP та TLS в один round trip. ### HTTP-запит і відповідь сервера Браузер надсилає GET-запит з заголовками: `Host`, `User-Agent`, `Accept-Encoding` і cookies для цього домену. Сервер відповідає кодом статусу і тілом. Відповідь 200 з HTML запускає рендеринг. 301 або 302 перезапускає весь процес з новим URL. Chunked transfer encoding дозволяє браузеру починати парсинг HTML ще до того, як прийшла повна відповідь. Тому сторінки іноді рендеряться поступово, а не з'являються одразу цілком. ### Побудова DOM і CSSOM Парсинг HTML - інкрементальний. Браузер токенізує потік байтів, створює вузли і додає їх до дерева по ходу читання. Повний документ не потрібен. CSS інакший. CSSOM (об'єктна модель стилів) має бути побудований повністю перед використанням - пізніші правила можуть перекривати ранні, тому браузеру потрібна повна картина. Великий неоптимізований CSS-файл блокує формування render tree. Саме тому розбивка CSS за media query або відкладення некритичних стилів має значення. ### JavaScript і блокування рендерингу Тег `<script>` без атрибутів зупиняє парсинг HTML повністю. Браузер завантажує скрипт, виконує його і тільки потім продовжує. Це зроблено тому, що JS може викликати `document.write()` або змінити DOM під час парсингу. `async` завантажує скрипт паралельно, але все одно призупиняє парсинг під час виконання. `defer` завантажує паралельно і виконує тільки після повного парсингу HTML, у порядку документа. Для більшості скриптів `defer` - правильний вибір. Я бачив, як команди витрачали по дві години на дебаг проблем з порядком завантаження через `async` на скриптах, що залежали один від одного. Порядок виконання з `async` не гарантований. ### Render tree Render tree - це не DOM. Він поєднує кожен видимий DOM-вузол з його обчисленими стилями з CSSOM. `<head>`, `<script>` і будь-який елемент з `display: none` виключаються. Елемент з `visibility: hidden` залишається в дереві і займає місце - він впливає на layout. ### Layout (reflow) Layout обчислює точний бокс для кожного вузла render tree: позицію, ширину, висоту, відступи відносно viewport. Саме тут реально застосовується CSS-блокова модель. Layout ресурсоємний. Зміна ширини контейнера може викликати перерахунок для всіх його дочірніх елементів. Зчитування layout-властивості (`offsetHeight`, `getBoundingClientRect`) після запису в стиль змушує браузер синхронно виконати layout для повернення актуального значення. Це layout thrashing, і він вбиває частоту кадрів. ### Paint і composite Paint перетворює кожен шар на пікселі: кольори, текст, рамки, тіні. Шари малюються від нижнього до верхнього (алгоритм художника). Кожен шар растеризується незалежно. Composite відбувається на GPU. Елементи з `transform`, `opacity`, `will-change` або `position: fixed` часто отримують власний compositor-шар. GPU об'єднує їх і відправляє кадр на екран. Саме тому анімації через `transform` плавні: вони ніколи не запускають layout або paint, тільки composite. ### Типові помилки **Блокуючі скрипти в `<head>`** ```html <!-- блокує парсинг під час завантаження і виконання --> <head> <script src="app.js"></script> </head> <!-- завантажує паралельно, виконує після парсингу - без блокування --> <head> <script src="app.js" defer></script> </head> ``` **Layout thrashing у циклі** ```javascript // Погано: читання скидає черговий layout, запис одразу забруднює знову elements.forEach(el => { const h = el.offsetHeight; // читання: синхронний layout el.style.height = (h + 10) + 'px'; // запис: layout знову dirty }); // Добре: спочатку всі читання, потім всі записи const heights = elements.map(el => el.offsetHeight); // один layout elements.forEach((el, i) => { el.style.height = (heights[i] + 10) + 'px'; }); ``` **Анімація геометричних властивостей** ```css /* Повний пайплайн на кожен кадр: layout + paint + composite */ .slow-animation { transition: width 0.3s, left 0.3s; } /* Тільки composite - без layout, без paint */ .fast-animation { transition: transform 0.3s, opacity 0.3s; } ``` **Відсутність `defer` на сторонніх скриптах** Додавання аналітики чи чат-віджетів без `defer` може додати сотні мілісекунд до Time to Interactive на повільних з'єднаннях. Ці скрипти майже ніколи не потребують виконання до завершення парсингу HTML. ### Де зустрічається в реальних проектах - Chrome DevTools, вкладка Performance: жовтий - JS, фіолетовий - layout, зелений - paint, тонкі зелені смужки - composite - Lighthouse, прапорець "Eliminate render-blocking resources" - ловить стилі та скрипти, що затримують First Contentful Paint - React і Vue мінімізують кількість DOM-мутацій, що запускають layout і paint - вони не обходять жоден з цих кроків, просто зменшують їх кількість - `will-change: transform` переміщує елемент на власний compositor-шар до початку анімації, уникаючи накладних витрат на промоцію шару ### Питання для поглиблення **Q:** Що таке critical rendering path? **A:** Послідовність DOM, CSSOM, render tree, layout, paint. Оптимізація - це скорочення кількості ресурсів, що блокують цей шлях, і зменшення обсягу байтів на кожному кроці. **Q:** Яка різниця між reflow і repaint? **A:** Reflow перераховує геометрію - викликається зміною розмірів, позицій, шрифтів. Repaint перемальовує пікселі без зміни геометрії - колір, фон, видимість. Reflow завжди викликає repaint. Repaint не викликає reflow. **Q:** Чому `visibility: hidden` відрізняється від `display: none` з точки зору layout? **A:** `display: none` видаляє елемент з render tree повністю - він не займає місця. `visibility: hidden` залишає його в дереві з повним впливом на layout, тобто елемент і далі займає простір і зміщує сусідів. **Q:** Як HTTP/2 впливає на мережеву фазу? **A:** HTTP/2 мультиплексує всі запити через одне TCP-з'єднання, прибираючи витрати на handshake для кожного ресурсу. Також стискає заголовки через HPACK і підтримує server push. **Q:** Сценарій рівня senior: ти читаєш `el.offsetWidth`, а потім встановлюєш `el.style.width` всередині `requestAnimationFrame`-колбека на кожному кадрі. Що відбувається і чому? **A:** Читання `offsetWidth` змушує браузер синхронно виконати layout для повернення актуального значення. Після запису в `style.width` layout позначається dirty знову. На наступному кадрі - ще один примусовий layout. Це layout thrashing всередині rAF. Він відбувається навіть там, якщо читання і запис чергуються, і є однією з найчастіших причин dropped frames. ## Приклади ### Трейсинг завантаження сторінки в Chrome DevTools Відкрий DevTools, перейди на вкладку Network, клікни на HTML-запит і відкрий панель Timing: - "DNS Lookup": час на DNS-резолвінг - "Initial Connection": TCP handshake - "SSL": TLS-переговори (тільки HTTPS) - "Time to First Byte": час обробки на сервері плюс початок відповіді - "Content Download": отримання байтів HTML На вкладці Performance запиши перезавантаження. Flame chart на Main thread відображає кожен етап рендерингу: фіолетовий - layout, зелений - paint, жовтий - виконання JS. Composite - тонкі зелені смужки на Compositor thread. ### Стратегії завантаження скриптів ```html <!-- За замовчуванням: парсер блокується під час завантаження і виконання --> <script src="heavy-lib.js"></script> <!-- async: завантажує паралельно, виконує одразу як готовий --> <!-- порядок не гарантований, тільки для незалежних скриптів --> <script src="analytics.js" async></script> <!-- defer: завантажує паралельно, виконує після парсингу HTML, по порядку --> <!-- підходить для основного коду, всього що читає або змінює DOM --> <script src="app.js" defer></script> <script src="components.js" defer></script> <!-- components.js завжди виконується після app.js --> ``` Правило: `defer` для всього, що торкається DOM. `async` тільки для дійсно незалежних скриптів, де порядок виконання не має значення - трекери помилок, пікселі аналітики. ### Compositor layers та продуктивність анімацій ```javascript const box = document.querySelector('.box'); // Повільно: запускає layout + paint + composite на кожному кадрі function animateSlow() { let x = 0; function tick() { x += 2; box.style.left = x + 'px'; // layout dirty → reflow → repaint requestAnimationFrame(tick); } requestAnimationFrame(tick); } // Швидко: працює на compositor thread, без layout і paint function animateFast() { let x = 0; function tick() { x += 2; box.style.transform = `translateX(${x}px)`; // тільки composite requestAnimationFrame(tick); } requestAnimationFrame(tick); } ``` Додай `will-change: transform` в CSS елемента, щоб перемістити його на власний compositor-шар до початку анімації. Це уникне накладних витрат на промоцію шару на першому кадрі.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.