За долю секунды после получения HTML-файла браузер проделывает колоссальную работу: разбирает текст, строит дерево объектов, вычисляет стили, рассчитывает геометрию и рисует пиксели на экране. Понимание этого процесса — ключ к созданию быстрых интерфейсов.
Современный браузер — сложная система из нескольких компонентов:
Каждая вкладка в современном браузере — отдельный процесс операционной системы. Это изоляция безопасности: если одна вкладка зависнет, остальные продолжат работу.
Движок рендеринга читает HTML-текст символ за символом. Токенайзер превращает текст в токены (<div, class="container", >), а парсер строит из них дерево объектов — DOM (Document Object Model).
DOM — это не HTML-текст, а живое дерево объектов в памяти. Каждый тег становится узлом. Вот почему document.getElementById('app') работает мгновенно — браузер уже построил это дерево.
Парсинг HTML устойчив к ошибкам. Если ты забудешь закрыть тег, браузер исправит ошибку автоматически. Это отличает HTML от XML, который при ошибке просто отказывает в парсинге.
Одновременно с HTML браузер парсит CSS и строит CSSOM (CSS Object Model) — дерево стилей. CSSOM учитывает каскадность, специфичность и наследование. В конечном объекте стилей каждый элемент содержит все применимые к нему правила.
CSS блокирует рендеринг: браузер не начнёт отрисовку до тех пор, пока не загрузит и не обработает весь CSS. Поэтому CSS-файлы нужно подключать в <head>, а не в конце страницы.
Браузер объединяет DOM и CSSOM в Render Tree — дерево только видимых элементов с вычисленными стилями. Элементы с display: none в Render Tree не попадают. visibility: hidden — попадают, но остаются невидимыми.
Браузер обходит Render Tree и вычисляет точное положение и размер каждого элемента в пикселях. Этот этап называется Layout или Reflow. Он учитывает viewport, box model, float, flexbox, grid — всю CSS-геометрию.
Браузер проходит по вычисленной геометрии и заполняет пиксели: рисует текст, цвета, изображения, тени, border-radius. Это происходит по слоям — некоторые элементы (с transform, opacity, will-change) выносятся на отдельные GPU-слои.
Готовые слои отправляются на GPU, который склеивает их в финальное изображение. GPU-операции (transform, opacity) не требуют layout и paint — это делает их идеальными для анимаций.
document.readyState отражает текущую стадию загрузки:
"loading" — HTML ещё парсится"interactive" — DOM построен, ресурсы (картинки, стили) ещё грузятся. Срабатывает событие DOMContentLoaded"complete" — всё загружено. Срабатывает событие loadПонимание разницы между DOMContentLoaded и load критично для правильной инициализации скриптов.
Измерение этапов загрузки страницы и изменений readyState
// Отслеживаем изменения readyState
// В реальном коде этот скрипт должен быть в <head>
console.log('Начальный readyState:', document.readyState)
document.addEventListener('readystatechange', () => {
const state = document.readyState
const time = performance.now().toFixed(1)
console.log(`readyState изменился: "${state}" — через ${time} мс`)
})
// DOMContentLoaded — DOM готов, можно работать с элементами
// Срабатывает раньше load
document.addEventListener('DOMContentLoaded', () => {
const timing = performance.now()
console.log(`DOMContentLoaded: ${timing.toFixed(1)} мс`)
// Теперь можно безопасно обращаться к DOM-элементам
const allElements = document.querySelectorAll('*').length
console.log(`Элементов в DOM: ${allElements}`)
})
// load — всё загружено: картинки, стили, шрифты
window.addEventListener('load', () => {
const timing = performance.now()
console.log(`load: ${timing.toFixed(1)} мс`)
// Navigation Timing API — детальная статистика загрузки
const nav = performance.getEntriesByType('navigation')[0]
console.log('DNS:', (nav.domainLookupEnd - nav.domainLookupStart).toFixed(1), 'мс')
console.log('DOM парсинг:', (nav.domContentLoadedEventStart - nav.responseEnd).toFixed(1), 'мс')
console.log('Полная загрузка:', nav.loadEventEnd.toFixed(1), 'мс')
})За долю секунды после получения HTML-файла браузер проделывает колоссальную работу: разбирает текст, строит дерево объектов, вычисляет стили, рассчитывает геометрию и рисует пиксели на экране. Понимание этого процесса — ключ к созданию быстрых интерфейсов.
Современный браузер — сложная система из нескольких компонентов:
Каждая вкладка в современном браузере — отдельный процесс операционной системы. Это изоляция безопасности: если одна вкладка зависнет, остальные продолжат работу.
Движок рендеринга читает HTML-текст символ за символом. Токенайзер превращает текст в токены (<div, class="container", >), а парсер строит из них дерево объектов — DOM (Document Object Model).
DOM — это не HTML-текст, а живое дерево объектов в памяти. Каждый тег становится узлом. Вот почему document.getElementById('app') работает мгновенно — браузер уже построил это дерево.
Парсинг HTML устойчив к ошибкам. Если ты забудешь закрыть тег, браузер исправит ошибку автоматически. Это отличает HTML от XML, который при ошибке просто отказывает в парсинге.
Одновременно с HTML браузер парсит CSS и строит CSSOM (CSS Object Model) — дерево стилей. CSSOM учитывает каскадность, специфичность и наследование. В конечном объекте стилей каждый элемент содержит все применимые к нему правила.
CSS блокирует рендеринг: браузер не начнёт отрисовку до тех пор, пока не загрузит и не обработает весь CSS. Поэтому CSS-файлы нужно подключать в <head>, а не в конце страницы.
Браузер объединяет DOM и CSSOM в Render Tree — дерево только видимых элементов с вычисленными стилями. Элементы с display: none в Render Tree не попадают. visibility: hidden — попадают, но остаются невидимыми.
Браузер обходит Render Tree и вычисляет точное положение и размер каждого элемента в пикселях. Этот этап называется Layout или Reflow. Он учитывает viewport, box model, float, flexbox, grid — всю CSS-геометрию.
Браузер проходит по вычисленной геометрии и заполняет пиксели: рисует текст, цвета, изображения, тени, border-radius. Это происходит по слоям — некоторые элементы (с transform, opacity, will-change) выносятся на отдельные GPU-слои.
Готовые слои отправляются на GPU, который склеивает их в финальное изображение. GPU-операции (transform, opacity) не требуют layout и paint — это делает их идеальными для анимаций.
document.readyState отражает текущую стадию загрузки:
"loading" — HTML ещё парсится"interactive" — DOM построен, ресурсы (картинки, стили) ещё грузятся. Срабатывает событие DOMContentLoaded"complete" — всё загружено. Срабатывает событие loadПонимание разницы между DOMContentLoaded и load критично для правильной инициализации скриптов.
Измерение этапов загрузки страницы и изменений readyState
// Отслеживаем изменения readyState
// В реальном коде этот скрипт должен быть в <head>
console.log('Начальный readyState:', document.readyState)
document.addEventListener('readystatechange', () => {
const state = document.readyState
const time = performance.now().toFixed(1)
console.log(`readyState изменился: "${state}" — через ${time} мс`)
})
// DOMContentLoaded — DOM готов, можно работать с элементами
// Срабатывает раньше load
document.addEventListener('DOMContentLoaded', () => {
const timing = performance.now()
console.log(`DOMContentLoaded: ${timing.toFixed(1)} мс`)
// Теперь можно безопасно обращаться к DOM-элементам
const allElements = document.querySelectorAll('*').length
console.log(`Элементов в DOM: ${allElements}`)
})
// load — всё загружено: картинки, стили, шрифты
window.addEventListener('load', () => {
const timing = performance.now()
console.log(`load: ${timing.toFixed(1)} мс`)
// Navigation Timing API — детальная статистика загрузки
const nav = performance.getEntriesByType('navigation')[0]
console.log('DNS:', (nav.domainLookupEnd - nav.domainLookupStart).toFixed(1), 'мс')
console.log('DOM парсинг:', (nav.domContentLoadedEventStart - nav.responseEnd).toFixed(1), 'мс')
console.log('Полная загрузка:', nav.loadEventEnd.toFixed(1), 'мс')
})Напиши код, который фиксирует момент начала выполнения скрипта и измеряет, сколько времени прошло до DOMContentLoaded и до load. Выведи результаты в консоль в виде: "DOMContentLoaded через X мс" и "load через Y мс".
Событие DOMContentLoaded слушается на document, событие load — на window. Время разницы: performance.now() минус startTime, записанный до регистрации слушателей.