← Браузер/Критический путь рендеринга#176 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: async и сетьТермин: Event LoopТермин: Core Web Vitals

Критический путь рендеринга

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

Что блокирует рендеринг

Браузер не может отрисовать страницу, пока не построит Render Tree. Render Tree требует и DOM, и CSSOM. Поэтому:

CSS блокирует рендеринг. Пока не загружен и не обработан CSS, браузер не рисует ни одного пикселя. Подключение стилей в конце страницы не ускоряет рендеринг — наоборот, вызывает «мигание» (FOUC, Flash of Unstyled Content).

Синхронный JS блокирует и парсинг HTML, и рендеринг. Когда браузер встречает <script> без атрибутов, он останавливает парсинг HTML, выполняет скрипт и только потом продолжает. Причина: скрипт может вызвать document.write() или изменить DOM, что сделало бы уже построенную часть дерева неактуальной.

defer и async

Атрибуты defer и async позволяют браузеру загружать скрипты не блокируя парсинг HTML.

`async` — скрипт загружается параллельно, но выполняется немедленно по готовности, прерывая парсинг HTML. Порядок выполнения непредсказуем. Подходит для независимых скриптов: аналитика, счётчики.

`defer` — скрипт загружается параллельно и выполняется после полного парсинга HTML, но до DOMContentLoaded. Порядок выполнения соответствует порядку в HTML. Это предпочтительный вариант для большинства скриптов.

preload и prefetch

`<link rel="preload">` — говорит браузеру: «загрузи этот ресурс как можно скорее, он понадобится прямо сейчас». Используется для критических ресурсов: главный шрифт, фоновое изображение hero-блока.

`<link rel="prefetch">` — загрузи этот ресурс с низким приоритетом, он понадобится на следующей странице. Браузер сохранит его в кэш.

Метрики: FCP и LCP

FCP (First Contentful Paint) — момент, когда браузер впервые отрисовал что-либо: текст, картинку, SVG. Хороший FCP — менее 1.8 секунды.

LCP (Largest Contentful Paint) — момент отрисовки наибольшего видимого элемента: обычно это главное изображение или заголовок. Хороший LCP — менее 2.5 секунды. Это одна из Core Web Vitals и влияет на SEO.

Разница между FCP и LCP показывает, как быстро загружается главный контент после первого появления чего-либо на экране.

Оптимизация критического пути

  • Минимизируй CSS выше сгиба (above the fold) — стили для видимой части
  • Инлайни критический CSS прямо в HTML через <style>
  • Выноси некритический CSS в async-загрузку через media="print" + JavaScript
  • Переноси скрипты в конец <body> или используй defer
  • Используй preload для критических ресурсов
  • Оптимизируй изображения: WebP, правильный размер, loading="lazy" для некритических
  • Медленные соединения

    navigator.connection даёт информацию о типе соединения: тип (4g, wifi, ethernet), эффективный тип (slow-2g, 2g, 3g, 4g) и ожидаемую задержку. Это позволяет адаптировать загрузку ресурсов под скорость соединения пользователя.

    Примеры

    Измерение FCP и LCP через PerformanceObserver, проверка типа соединения

    // PerformanceObserver — наблюдает за метриками производительности в реальном времени
    // Измеряем FCP (First Contentful Paint)
    const fcpObserver = new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach(entry => {
        console.log(`FCP: ${entry.startTime.toFixed(0)} мс`)
        // Хорошо: < 1800 мс, Нужно улучшить: 1800-3000 мс, Плохо: > 3000 мс
        const rating = entry.startTime < 1800 ? 'Хорошо' :
                       entry.startTime < 3000 ? 'Средне' : 'Плохо'
        console.log('Оценка FCP:', rating)
      })
    })
    fcpObserver.observe({ type: 'paint', buffered: true })
    
    // Измеряем LCP (Largest Contentful Paint)
    const lcpObserver = new PerformanceObserver((list) => {
      // LCP обновляется по мере загрузки — берём последнее значение
      const entries = list.getEntries()
      const lastEntry = entries[entries.length - 1]
      console.log(`LCP: ${lastEntry.startTime.toFixed(0)} мс`)
      console.log('LCP элемент:', lastEntry.element?.tagName)
    })
    lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true })
    
    // Информация о соединении пользователя
    if ('connection' in navigator) {
      const conn = navigator.connection
      console.log('Тип соединения:', conn.effectiveType)  // 4g, 3g, 2g, slow-2g
      console.log('Ожидаемая задержка (RTT):', conn.rtt, 'мс')
      console.log('Скорость загрузки:', conn.downlink, 'Мбит/с')
    
      // Адаптируем качество под скорость
      if (conn.effectiveType === 'slow-2g' || conn.effectiveType === '2g') {
        console.log('Медленное соединение — загружаем облегчённые ресурсы')
      }
    }

    Критический путь рендеринга

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

    Что блокирует рендеринг

    Браузер не может отрисовать страницу, пока не построит Render Tree. Render Tree требует и DOM, и CSSOM. Поэтому:

    CSS блокирует рендеринг. Пока не загружен и не обработан CSS, браузер не рисует ни одного пикселя. Подключение стилей в конце страницы не ускоряет рендеринг — наоборот, вызывает «мигание» (FOUC, Flash of Unstyled Content).

    Синхронный JS блокирует и парсинг HTML, и рендеринг. Когда браузер встречает <script> без атрибутов, он останавливает парсинг HTML, выполняет скрипт и только потом продолжает. Причина: скрипт может вызвать document.write() или изменить DOM, что сделало бы уже построенную часть дерева неактуальной.

    defer и async

    Атрибуты defer и async позволяют браузеру загружать скрипты не блокируя парсинг HTML.

    `async` — скрипт загружается параллельно, но выполняется немедленно по готовности, прерывая парсинг HTML. Порядок выполнения непредсказуем. Подходит для независимых скриптов: аналитика, счётчики.

    `defer` — скрипт загружается параллельно и выполняется после полного парсинга HTML, но до DOMContentLoaded. Порядок выполнения соответствует порядку в HTML. Это предпочтительный вариант для большинства скриптов.

    preload и prefetch

    `<link rel="preload">` — говорит браузеру: «загрузи этот ресурс как можно скорее, он понадобится прямо сейчас». Используется для критических ресурсов: главный шрифт, фоновое изображение hero-блока.

    `<link rel="prefetch">` — загрузи этот ресурс с низким приоритетом, он понадобится на следующей странице. Браузер сохранит его в кэш.

    Метрики: FCP и LCP

    FCP (First Contentful Paint) — момент, когда браузер впервые отрисовал что-либо: текст, картинку, SVG. Хороший FCP — менее 1.8 секунды.

    LCP (Largest Contentful Paint) — момент отрисовки наибольшего видимого элемента: обычно это главное изображение или заголовок. Хороший LCP — менее 2.5 секунды. Это одна из Core Web Vitals и влияет на SEO.

    Разница между FCP и LCP показывает, как быстро загружается главный контент после первого появления чего-либо на экране.

    Оптимизация критического пути

  • Минимизируй CSS выше сгиба (above the fold) — стили для видимой части
  • Инлайни критический CSS прямо в HTML через <style>
  • Выноси некритический CSS в async-загрузку через media="print" + JavaScript
  • Переноси скрипты в конец <body> или используй defer
  • Используй preload для критических ресурсов
  • Оптимизируй изображения: WebP, правильный размер, loading="lazy" для некритических
  • Медленные соединения

    navigator.connection даёт информацию о типе соединения: тип (4g, wifi, ethernet), эффективный тип (slow-2g, 2g, 3g, 4g) и ожидаемую задержку. Это позволяет адаптировать загрузку ресурсов под скорость соединения пользователя.

    Примеры

    Измерение FCP и LCP через PerformanceObserver, проверка типа соединения

    // PerformanceObserver — наблюдает за метриками производительности в реальном времени
    // Измеряем FCP (First Contentful Paint)
    const fcpObserver = new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach(entry => {
        console.log(`FCP: ${entry.startTime.toFixed(0)} мс`)
        // Хорошо: < 1800 мс, Нужно улучшить: 1800-3000 мс, Плохо: > 3000 мс
        const rating = entry.startTime < 1800 ? 'Хорошо' :
                       entry.startTime < 3000 ? 'Средне' : 'Плохо'
        console.log('Оценка FCP:', rating)
      })
    })
    fcpObserver.observe({ type: 'paint', buffered: true })
    
    // Измеряем LCP (Largest Contentful Paint)
    const lcpObserver = new PerformanceObserver((list) => {
      // LCP обновляется по мере загрузки — берём последнее значение
      const entries = list.getEntries()
      const lastEntry = entries[entries.length - 1]
      console.log(`LCP: ${lastEntry.startTime.toFixed(0)} мс`)
      console.log('LCP элемент:', lastEntry.element?.tagName)
    })
    lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true })
    
    // Информация о соединении пользователя
    if ('connection' in navigator) {
      const conn = navigator.connection
      console.log('Тип соединения:', conn.effectiveType)  // 4g, 3g, 2g, slow-2g
      console.log('Ожидаемая задержка (RTT):', conn.rtt, 'мс')
      console.log('Скорость загрузки:', conn.downlink, 'Мбит/с')
    
      // Адаптируем качество под скорость
      if (conn.effectiveType === 'slow-2g' || conn.effectiveType === '2g') {
        console.log('Медленное соединение — загружаем облегчённые ресурсы')
      }
    }

    Задание

    Напиши код, который создаёт PerformanceObserver для отслеживания LCP. При каждом обновлении выводи в консоль время LCP и тег элемента. После трёх секунд отключи наблюдатель методом disconnect().

    Подсказка

    Тип события — "largest-contentful-paint". Время хранится в entry.startTime, тег элемента — в entry.element.tagName. Метод отключения наблюдателя называется disconnect().

    Загружаем среду выполнения...
    Загружаем AI-помощника...