Антипаттерн для веб-шрифтов: Data URI

Перевод статьи WEB FONT ANTI-PATTERN: DATA URIS с сайта zachleat.com, опубликовано на css-live.ru с разрешения автора — Зака Лезермана.

После того, как я поделился своей статьей «Минимально необходимые шрифты» в Твиттере, у меня состоялся занимательный разговор с одним разработчиком по имени Вим Лирс про веб-шрифты в виде Data URI.

Вим Лирс (@wimleers), твит.

Почему бы просто не встроить шрифты как data URI в минимально необходимом CSS? Грузится из кеша, никакого мелькания неоформленного текста. Пример: http://wimleers.com/

Он предложил встраивать шрифт прямо в блок стилей в разметке, генерируемой сервером, например, так:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <style>
    @font-face {
        font-family: Open Sans;
        src: url("data:application/x-font-woff;charset=utf-8;base64,...") format("woff");
        font-weight: 400;
        font-style: normal;
    }
    </style>
</head>
…
</html>

Такой подход не следует путать с асинхронным подходом loadCSS Data URI, который описан в блоге Filament Group, но признан устаревшим

Я встречал подобный подход с Data URI на Alibaba.com, правда вместо встроенных стилей они используют внешнюю таблицу стилей. Я немного рассказывал об этом на Velocity в прошлом году.

По мне такой подход является антипаттерном для обычных сценариев загрузки шрифта по нескольким причинам:

  1. Он помещает большой Data URI в CSS, обязательный для начальной загрузки. Помните, что CSS блокирует отображение. Задача состоит в том, чтобы избежать мелькания невидимого текста (англ. flash of invisible text — FOIT) и минимизировать мелькание неоформленного текста (англ. flash of unstyled text — FOUT). Очевидно, что задерживать отображение всей страницы во избежание FOIT и FOUT — не лучший вариант. Поскольку 42% сайтов загружают свыше 40КБ шрифтов, для большинства сайтов это будет означать добавку 40КБ Data URI в стили для первоначального отображения, что намного превышает рекомендованные для моментально отображаемого контента рамки в 14КБ.
  2. Встроенный вами формат шрифта, вероятно, не оптимален. Если вы встраиваете Data URI, то скорее всего встроите формат WOFF, чтобы точно уж быть уверенным в поддержке браузера, даже несмотря на то, что WOFF2 обычно весит на 30% меньше. Встраивание единственного формата лишает нас возможности автоматически выбрать самый подходящий, как в случае типичного значения атрибута src со списком форматов через запятую. В принципе, здесь можно указать и несколько src, но, предположим, вы встраиваете Data URI формата WOFF2, а в качестве альтернативного внешнего url в атрибуте src укажете формат WOFF. Далеко не все современные браузеры поддерживают WOFF2, некоторым всё равно пришлось бы загрузить такой большой Data URI и им всё равно в итоге придется загрузить резервный формат по ссылке. (Смотрите ниже Приложение 1, Data URI и резервный src.)
  3. Страдает и возможность кеширования шрифтов. Этот недостаток особенно заметен при повторных загрузках, поскольку Data URI тесно связан с разметкой и не будет кешироваться (если только пользователь не посетит одну страницу дважды).
  4. Другой недостаток упоминается в последней презентации Брэма Стейна (и даже показан на великолепной каскадной диаграмме): если взять несколько веб-шрифтов и встроить их как Data URI, то это заставит их загружаться последовательно (что плохо), а не параллельно (что хорошо).

В силу этих причин этот метод считается антипаттерном и не должен применяться в реальных проектах. На первый взгляд он может показаться полезным, но на самом деле он вредит производительности.

Но чисто ради интереса давайте применим это на практике и посмотрим, как это скажется на шрифтах моего сайта.

(Миллисекунды получены с помощью «Имитации медленного соединения (обычного 3G)» в инструментах разработчика Chrome)

  Традиционная загрузка шрифта Data URI обычного шрифта …и курсива …и жирного …и жирного курсива
Первая отрисовка 573мс

 

 

54КБ HTML
953мс

 

 

(+66%)
95.7КБ HTML
1.27с

 

 

(+33%)
133КБ HTML
1.94с

 

 

(+52%)
175КБ HTML
2.30с

 

 

(+18%)
212КБ HTML
Обычный шрифт загрузился 2.12с 1.01с

 

 

(-52%)
1.53с

 

 

(+51%)
2.03с

 

 

(+32%)
2.38с

 

 

(+17%)
Курсив загрузился 2.12с 2.05с

 

 

(-3%)
1.53с

 

 

(-25%)
2.03с

 

 

(+32%)
2.38с

 

 

(+17%)
Жирный загрузился 2.20с 2.11с

 

 

(-4%)
2.16с

 

 

(+2%)
2.03с

 

 

(-6%)
2.38с

 

 

(+17%)
Жирный курсив загрузился н/д н/д н/д н/д 2.38с

Интересно, что если вы встраиваете только обычный шрифт и готовы заплатить лишними почти 400мс времени первого отображения (ого, это большая жертва!), то можно сэкономить целую секунду при отображении этого шрифта. Еще больше достаётся производительности от встраивания второго шрифта, а встраивать больше двух шрифтов, даже без учета опасности класть все яйца в одну корзину, с точки зрения производительности ничуть не лучше, чем не делать ничего (сравните 1-ю и 4-ю колонки). Также заметьте, что в таблице выше я не тестировал повторные загрузки, поскольку данные и так были крайне отрицательными.

Но подождите…

Разговор начался со встраивания всего веб-шрифта, но что, если применить эту идею к подходу с минимально необходимым шрифтом? Что если встроить только минимально необходимое подмножество шрифта? Подозреваю, что файл подмножества шрифта WOFF весом 11КБ (гораздо меньше, чем типичные 40КБ) всё равно слишком велик, чтобы поместиться в первоочередное отображение, но давайте это проверим.

(Миллисекунды получены с помощью «Имитации медленного соединения (обычного 3G)» в инструментах разработчика Chrome)

Первый запрос страницы
  Минимально необходимый шрифт Минимально необходимый обычный шрифт в Data URI
Первая отрисовка 570мс

 

 

58.2КБ HTML
585мс

 

 

(+2.6%)
72.1КБ HTML
Минимально необходимый обычный шрифт загрузился 967мс 585мс

 

 

(-39%)
Весь обычный шрифт загрузился 2.70с 2.44с

 

 

(-9%)
Курсив загрузился 2.70с 2.44с

 

 

(-9%)
Жирный загрузился 2.70с 2.44с

 

 

(-9%)
Жирный курсив загрузился 2.70с 2.44с

 

 

(-9%)

Ухты! Никаких видимых FOUT! Неплохой результат. Минимально необходимый шрифт доступен при первом отображении с пустым кешем в 3G. Это здорово! Единственная проблема здесь, пожалуй, в том, что для повторной загрузки Data URI по-прежнему встраиваются на страницу. Проверим это на практике:

Повторная отрисовка
  Минимально необходимый шрифт Минимально необходимый обычный шрифт в Data URI
Первая отрисовка 309мс 291мс

 

 

(-5.8%)
Минимально необходимый обычный шрифт загрузился 479мс 418мс

 

 

(-12%)
Весь обычный шрифт загрузился 479мс 418мс

 

 

(-12%)
Курсив загрузился 479мс 530мс

 

 

(+10%)
Жирный загрузился 479мс 418мс

 

 

(-12%)
Жирный курсив загрузился 479мс 418мс

 

 

(-12%)

Здесь цифры тоже самую чуточку лучше. Ха! Думаю, стоит повозиться с этим подходом на своём сайте и посмотреть, что из этого выйдет. Спасибо за разговор, Вим! Я почерпнул для себя, что если что-то считается антипаттерном, то это не значит, что вы должны выплескивать вместе с водой и ребенка. Можно получать пользу, используя часть подхода. Минимально необходимый шрифт в виде Data URI!

Приложение 1, Data URI и резервный src

@font-face {
    /* Во многих браузерах это загружает гигантский Data URI, но не может использовать его. */
    src: url("data:application/font-woff2;charset=utf-8;base64,...") format("woff2"), url( /path/to/webfont.woff ) format( "woff" );
}

P.S. Это тоже может быть интересно:

3 Комментарии

  1. Павел

    А если при первый раз за сессию вставлять data-uri + подгружать полный шрифт после загрузки всей страницы, пока пользователь уже смотрит страницу?
    Полный шрифт будет в кеше, мы запоминаем, что этому пользователю уже выдали полный шрифт и в дальнейшем уже не вставляем этот кусок в

    1. Павел

      в тег head

      1. SelenIT

        В принципе, можно, но понадобится заводить и отслеживать эту самую сессию для каждого посещения (дополнительная логика на сервере). А подход из статьи можно использовать и на статике.

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

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