Антипаттерн для веб-шрифтов: 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 в прошлом году.
По мне такой подход является антипаттерном для обычных сценариев загрузки шрифта по нескольким причинам:
- Он помещает большой Data URI в CSS, обязательный для начальной загрузки. Помните, что CSS блокирует отображение. Задача состоит в том, чтобы избежать мелькания невидимого текста (англ. flash of invisible text — FOIT) и минимизировать мелькание неоформленного текста (англ. flash of unstyled text — FOUT). Очевидно, что задерживать отображение всей страницы во избежание FOIT и FOUT — не лучший вариант. Поскольку 42% сайтов загружают свыше 40КБ шрифтов, для большинства сайтов это будет означать добавку 40КБ Data URI в стили для первоначального отображения, что намного превышает рекомендованные для моментально отображаемого контента рамки в 14КБ.
- Встроенный вами формат шрифта, вероятно, не оптимален. Если вы встраиваете Data URI, то скорее всего встроите формат WOFF, чтобы точно уж быть уверенным в поддержке браузера, даже несмотря на то, что WOFF2 обычно весит на 30% меньше. Встраивание единственного формата лишает нас возможности автоматически выбрать самый подходящий, как в случае типичного значения атрибута
src
со списком форматов через запятую. В принципе, здесь можно указать и несколькоsrc
, но, предположим, вы встраиваете Data URI формата WOFF2, а в качестве альтернативного внешнего url в атрибутеsrc
укажете формат WOFF. Далеко не все современные браузеры поддерживают WOFF2, некоторым всё равно пришлось бы загрузить такой большой Data URI и им всё равно в итоге придется загрузить резервный формат по ссылке. (Смотрите ниже Приложение 1, Data URI и резервный src.) - Страдает и возможность кеширования шрифтов. Этот недостаток особенно заметен при повторных загрузках, поскольку Data URI тесно связан с разметкой и не будет кешироваться (если только пользователь не посетит одну страницу дважды).
- Другой недостаток упоминается в последней презентации Брэма Стейна (и даже показан на великолепной каскадной диаграмме): если взять несколько веб-шрифтов и встроить их как 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. Это тоже может быть интересно:
А если при первый раз за сессию вставлять data-uri + подгружать полный шрифт после загрузки всей страницы, пока пользователь уже смотрит страницу?
Полный шрифт будет в кеше, мы запоминаем, что этому пользователю уже выдали полный шрифт и в дальнейшем уже не вставляем этот кусок в
в тег head
В принципе, можно, но понадобится заводить и отслеживать эту самую сессию для каждого посещения (дополнительная логика на сервере). А подход из статьи можно использовать и на статике.
А Вы не подскажите, почему в Linux (Centos 6) этот самый src: url(«data:application/x-font-woff;charset=utf-8;base64,…») при водит к кракозябрам вместо букв.
Вот пример: http://storage5.static.itmages.com/i/17/1101/h_1509525932_4876749_fe97bd2b96.png
А вот без : http://storage4.static.itmages.com/i/17/1101/h_1509526123_6384691_0985ce49c3.png
—
Правда есть такой нюанс, что не только это приводит к кракозябрям. Но еще формат ttf читается похожим образом src: url(‘/fonts/Fira/ttf/FiraSans-Book.ttf’) format(‘truetype’);
http://storage1.static.itmages.com/i/17/1101/h_1509526476_9966466_e68d4d1a44.png
http://storage7.static.itmages.com/i/17/1101/h_1509525825_1376346_56482b0c56.png