Оптимізація шрифтів триває з метою прискорення роботи сайтів: згідно HTTP Archive, ~37% топових сайтів використовують веб-шрифти на початок 2014 року, що означає двократне зростання у порівнянні з попереднім проміжком в 12 місяців. Звичайно, для більшості це вже не сюрприз.

Друкарня завжди була важливою частиною гарного дизайну, до того ж веб-шрифти надають багато додаткових переваг: текст можна виділити, здійснювати в ній пошук і зумувати без втрати якості.

Але як йдуть справи зі швидкістю відтворення? Чи Не позначається використання веб-шрифтів негативно на продуктивності?

Шрифти є критично важливим ресурсом сторінки, тому так, їх використання може вплинути на швидкість показу сторінки в браузері. Однак сам по собі факт використання веб-шрифтів не означає, що сторінка буде завантажуватися повільно.

Наведемо невеликий список факторів, що визначають вплив веб-шрифтів на швидкість завантаження сторінок:

  • Кількість підключених шрифтів і ступінь їх товщини;
  • Розмір файлів використовуваних шрифтів;
  • Швидкість передачі видалених файлів шрифтів;
  • Час початку завантаження веб-шрифтів.

Контроль над першими двома пунктами знаходиться в руках дизайнера сайту. Чим більше використано різних шрифтів, тим більше буде запитів і переданих байтів.

Варто зменшити кількість підключених шрифтів до мінімуму, в цілях прискорення завантаження. Отже, перший крок до оптимізації: використовуйте веб-шрифти, але намагайтеся знизити їх кількість до мінімуму.

Вимірювання затримки завантаження шрифтів

Затримка в передачі кожного файлу шрифтів залежить, зрозуміло, від його розміру, який визначається числом гліфів, розміром метаданих (наприклад, хінтінг (hinting) для Windows-платформ) і використовуваним методом компресії.

Техніки зразок часткового включення (font subsetting), UA-specific оптимізація і більш ефективна компресія (наприклад, сервіс Google Fonts нещодавно перейшов на Zopfli для WOFF-ресурсів), також дуже важливі для оптимізації розміру переданих файлів.

Плюс до цього, оскільки ми говоримо про затримки, має значення розташування самого файлу шрифтів – тобто CDN як не можна краще підходить для кеша!

Замість того, щоб говорити про абстрактних величинах, розберемося, скільки ж насправді займає скачування файлу веб-шрифту при завантаженні відвідувачем вашого сайту?

Кращим способом відповісти на це питання, є інструмент під назвою Resource Timing API, який дозволяє вам отримувати часові виміри DNS, TCP і передачі файлів для кожного шрифту. Google Fonts як раз нещодавно впровадили підтримку Resource Timing!

Нижче наведено фрагмент коду, заміряють затримки шрифтів в Google Analytics:

// перевірка: чи підтримує Resource Timing браузер відвідувача
if (typeof window.performance == ‘object’) {
if (typeof window.performance.getEntriesByName == ‘function’) {
function logData(name, r) {
var dns = Math.round(r.domainLookupEnd — r.domainLookupStart),
tcp = Math.round(r.connectEnd — r.connectStart),
total = Math.round(r.responseEnd — r.startTime);
_gaq.push(
[‘_trackTiming’, name, ‘dns’, dns],
[‘_trackTiming’, name, ‘tcp’, tcp],
[‘_trackTiming’, name, ‘total’, total]
);
}
var _gaq = _gaq || [];
var resources = window.performance.getEntriesByType(«resource»);
for (var i in resources) {
if (resources[i].name.indexOf(«themes.googleusercontent.com») != -1) {
logData(«webfont-font», resources[i])
}
if (resources[i].name.indexOf(«fonts.googleapis.com») != -1) {
logData(«webfont-css», resources[i])
}
}
}
}

Вищенаведений код заміряє основні параметри для UA-оптимізованих CSS-файлів і веб-шрифтів, визначених у ньому: CSS розташовується за адресою fonts.googleapis.com і кешується близько 24 годин, а файли шрифтів — на themes.googleusercontent.com і мають довготривалий проміжок зберігання.

Тепер, давайте поглянемо на всі дані в цілому (проміжок responseEnd — startTime) в Google Analytics для мого сайту:

Оптимізація відтворення веб-шрифтів

З міркувань безпеки, Resource Timing API навмисно не надає інформацію про витягнутих з кеша шрифтів, але ми, тим не менше, можемо, усереднивши, використовувати у своїй практиці величину до 20 мс. Чому стільки?

Завантаження кешованої інформації не «безкоштовна» з точки зору використання ресурсів. Реальна затримка витягування з кешу буде залежати від продуктивності заліза, але для наших цілей будемо використовувати агресивну величину до 20 мс.

Враховуючи згаданий вище факт і ґрунтуючись на наведеній статистиці відвідувань, отримаємо середній час завантаження файлу CSS, яке складе приблизно 100 мс. При цьому близько 26% відвідувачів завантажують його з локального кеша.

Після цього, нам потрібно завантажити необхідний файл(и) шрифтів, що в середньому займає <20мс – велика частина відвідувачів вже має їх у себе в кеші браузера! Це відмінна новина — концепція довго зберігаються і загальних для всіх сайтів ресурсів шрифтів працює.

Отримані результати будуть відрізнятися в залежності від використовуваних шрифтів, обсягу і типу трафіку і від інших змінних. Не потрібно говорити про затримки і продуктивності веб-шрифтів абстрактними термінами, адже у нас є конкретні інструменти і API для точного вимірювання всіх цих значень. А те, що ми можемо виміряти, ми можемо оптимізувати.

Обмеження по часу завантаження повільних ресурсів шрифтів

Незважаючи на спроби оптимізації завантаження файлів шрифтів, іноді у користувача може бути просто низькошвидкісної з’єднання з інтернетом через перевантажених каналів зв’язку, слабкого прийому і так далі.

У цьому випадку, критичні ресурси – включаючи файли шрифтів – можуть блокувати відображення сторінки, що ще більше погіршує становище.

Щоб успішно протистояти цьому, різні браузери використовують різні механізми:

  • IE відразу малює текст з використанням запасного шрифту і перемальовує його, як тільки буде завершено завантаження основного;
  • Firefox очікує завантаження оригінального файлу шрифтів 3 секунди, після чого, використовує запасний шрифт, а як тільки завантаження основного буде завершена – перемальовує текст, використовуючи його;
  • Google Chrome і Safari не отрисовывают сторінку до тих пір, поки завантаження шрифтів не буде завершена.
  • У кожного з вищенаведених походів є свої переваги і недоліки і навряд чи варто дискутувати на цю тему.

    Тим не менше, більшість погодиться, що відсутність якого-або тайм-ауту в Google Chrome та Safari це не надто розумний підхід, і саме цим вже деякий час займається команда розробників Google Chrome.

    Яким має бути значення тайм-ауту?

    Щоб відповісти на це питання, ми використовували Google Chrome для виміру часу вибірки шрифтів різного розміру, що дало наступні результати:

    Діапазон розміру шрифтів Відсоток 50 70 90 95 99
    0KB — 10KB 5.47% 136 мс 264 мс 785 мс 1.44 з 5.05 з
    10KB — 50KB 77.55% 111 мс 259 мс 892 мс 1.69 з 6.43 з
    50KB — 100KB 14.00% 167 мс 882 мс 1.31 з 2.54 з 9.74 з
    100KB — 1MB 2.96% 198 мс 534 мс 2.06 з 4.19 з 10+ з
    1MB+ 0.02% 370 мс 969 мс 4.22 з 9.21 з 10+ з

    Перша хороша новина полягає в тому, що велика частина веб-шрифтів відносно невеликого розміру (<50к).

    Друга – завантаження більшості шрифтів завершується за проміжок часу в кілька сотень мілісекунд: установка тайм-ауту в 10 секунд вплине лише на ~0.3% запитів завантаження шрифтів, а проміжок в 3 секунди тільки на ~1.1%.

    Грунтуючись на цих даних, можна зробити висновок, що оптимальним рішенням для Google Chrome було б скопіювати поведінку Firefox: тайм-аут після 3 секунд, потім використання запасного шрифту, а після завантаження основного – оновлення за допомогою нього.

    Така поведінка буде реалізовано в Google Chrome M35 і, сподіваюся, розробники Safari зроблять також.

    Практика: ініціалізація запиту завантаження ресурсу

    Отже, ми розібралися з тим, як виміряти затримку завантаження кожного ресурсу, але є ще одна змінна, про яку ми не згадали: потрібно також оптимізувати ініціалізацію завантаження.

    З першого погляду це може здатися очевидним, але стосовно до веб-шрифтів це може виявитися досить нетривіальним.

    Давайте розглянемо наступний приклад:

    @font-face {
    font-family: ‘FontB’;
    src: local(‘FontB’), url(‘http://mysite.com/fonts/fontB.woff’) format(‘woff’);
    }
    p { font-family: FontA }
    @font-face {
    font-family: ‘FontA’;
    src: local(‘FontA’), url(‘http://mysite.com/fonts/fontA.woff’) format(‘woff’);
    }

    Hello world!

    У цьому невеликому коді багато чого відбувається: у нас є зовнішні CSS і JavaScript- файли, а також inline-блок з кількома рядками CSS, включають два оголошення шрифтів.

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

    Давайте розберемося покроково:

  • Парсер документа шукає зовнішній файл stylesheet.css і запит відправляється;
  • Парсер документа обробляє inline-блок з CSS-кодом, у якому оголошено шрифт FontA. Ми хочемо, щоб запит шрифту ініціювалось як можна раніше, наскільки це можливо. Але цього не відбувається. Далі ми ще повернемося до цього питання. А поки продовжується виконання наступних пунктів;
  • Парсер документа блокується зовнішнім скриптом: ми не можемо продовжити до тих пір, поки його завантаження не буде завершена;
  • Після закінчення завантаження скрипт запускається і формується остаточне DOM-дерево, розраховуються стилі, і виконується позиціонування блоків, після чого ми, нарешті, відправляємо запит на завантаження шрифту fontA. У цей момент, ми вже можемо розпочати малювання, але текст з використанням обраного нами шрифту поки не може бути виведено, так як сам файл зі шрифтом все ще завантажується…
  • Ключовим моментом у проведених міркуваннях є те, що запит конкретного шрифту не ініціюється до того, як браузер буде знати, що він потрібен для відтворення будь-якого контенту на сторінці – тобто шрифт FontB ніколи не буде запитано, якщо на сторінці немає контенту, його використовує!

    З одного боку це добре, оскільки мінімізує число завантажень. З іншого ж боку, це також означає, що браузер не може ініціювати запит завантаження шрифту, поки не будуть побудовані DOM- і CSSOM— дерева, проаналізувавши які з’ясується, які шрифти потрібні для даної сторінки.

    У прикладі, наведеному вище, наш зовнішній JavaScript— файл блокує побудова DOM-дерева до тих пір, поки його (скрипта) завантаження не буде завершена і він не буде виконаний, що вносить затримку в скачування шрифту.

    Щоб це виправити, у нас є кілька варіантів: (a) прибрати JavaScript-код, (b) додати атрибут async для асинхронної завантаження скрипта (якщо це можливо) або (c) перемістити підключення скриптів вниз сторінки.

    Однак найбільш важливим є те, що завантаження шрифту не почнеться, поки не буде побудовано дерево. Щоб прискорити малювання шрифтів, нам потрібно оптимізувати критичні шляхи рендеринга на сторінці.

    Порада: на додаток до вимірювання відносних затримок запитів для кожного ресурсу, ми також можемо виміряти і проаналізувати початковий час запиту з допомогою Resource Timing! Відстеження цього значення дозволить нам визначити, коли був зроблений запит на завантаження шрифту.

    Оптимізація завантаження шрифтів у Google Chrome M33

    У Google Chrome M33 був зроблений важливий оптимізаційний крок, який значно поліпшив продуктивність рендеринга шрифтів.

    Щоб зрозуміти, що конкретно було зроблено, поглянемо на тимчасову шкалу обробки сторінки в Google Chrome версії pre-M33, яка ілюструє це:

    Оптимізація відтворення веб-шрифтів

    • Розрахунок стилів для сторінки був завершений за ~840 мс;
    • Позиціонування блоків було запущено по закінченні ~1040 мс з моменту початку обробки сторінки, після чого, майже відразу ж ініціювалось запит ресурсу шрифту.

    Однак чому ми повинні очікувати виконання позиціонування, якщо розрахунок стилів вже проведений двома сотнями мілісекунд раніше?

    Так як ми знаємо значення стилів, то можемо з’ясувати, завантаження яких шрифтів нам необхідно ініціювати прямо зараз – це нова модель поведінки в Google Chrome M33!

    З першого погляду, такий оптимізаційний хід може здатися не надто значущим, але ґрунтуючись на вимірах з допомогою Google Chrome проміжок між розрахунком стилів і позиціонуванням блоків дійсно більше, ніж можна подумати:

    Відсотки 50 60 70 80 90
    Час від початку розрахунку стилів до початку позиціонування блоків 132 мс 182 мс 259 мс 410 мс 820 мс

    Запускаючи запит на завантаження шрифтів негайно після розрахунку першого стилю, ми економимо в середньому близько 130мс, а при відсотку дорівнює 90 — економія становить близько 800 мс!

    Маніпулюючи цими параметрами, ми можемо керувати затримками завантаження шрифтів. Як ми бачили раніше, це допомагає почати завантаження шрифту до того, як буде вироблено позиціонування блоків, а це означає, що блокування виводу тексту відбуватися не буде.

    Це величезне поліпшення продуктивності!

    Звичайно, можна задати очевидне питання… Чому затримка між розрахунком стилів і позиціонуванням блоків так велика? Почнемо з Google Chrome DevTools: подивіться на тимчасову лінійку і виявити найбільш повільні операції (наприклад, довго запускаються скрипти і так далі). Потім, якщо відчуваєте в собі запал, то наберіть в адресному рядку chrome://tracing щоб побачити, що відбувається насправді, – може бути браузер просто зайнятий обробкою і позиціонуванням блоків на сторінці.

    Оптимізація веб-шрифтів за допомогою Font Load Events API

    І нарешті, ми підійшли до самої епічної частини даної статті: Font Load Events API. У двох словах, це API, який дозволить нам керувати і визначати, як і коли будуть завантажені шрифти – ми можемо планувати завантаження шрифтів, а також визначати, як і коли шрифт буде відображений і багато іншого.

    Якщо ви знайомі з JS-бібліотекою Web Font Loader, то розглядайте цей API як аналогічний, але більш інтегрований в браузер:

    var font = new FontFace(«FontA», «url(http://mysite.com/fonts/fontA.woff)», {});
    font.ready().then(function() {
    // шрифт завантажений.. можна визначити власну поведінку.
    });
    font.load(); // негайний запуск завантаження / don’t block on render tree!

    Font Load Events API дає нам повний контроль над тим, які використовуються шрифти, коли вони будуть завантажені (тобто вони повинні блокувати візуалізацію), а також коли вони викачуються.

    У наведеному вище прикладі, ми створили об’єкт FontFace безпосередньо в коді JavaScript і викликали тим самим негайну завантаження шрифту – ми можемо вставити цей фрагмент у верхню частину нашої сторінки і повністю уникнути блокування CSSOM- і DOM дерев!

    Радимо вам поекспериментувати з цим API в Google Chrome Canary – спеціальної версії для розробників, і якщо все піде добре, реалізувати всі в стабільній версії M35.

    Підіб’ємо підсумки

    Веб-шрифти дають багато переваг: покращена читаність, доступність (можливість виділення, пошуку і зумування).

    Все це дає прекрасні результати. Питання не в тому, чи використовувати веб-шрифти на шкоду продуктивності, а в тому, щоб їх грамотно оптимізувати.

    Давайте складемо підсумковий список етапів оптимізації:

  • Проведіть перевірку використання веб-шрифтів і виключіть ті, від яких можна відмовитися;
  • Переконайтеся, що ресурси шрифти оптимізовані;
  • Зробіть виміри ресурсів шрифтів за допомогою Resource Timing: з допомогою пункту «measure ; optimize»;
  • Оптимізуйте затримку передачі і час ініціалізації запиту на завантаження кожного шрифту;
  • Оптимізуйте критичний шлях рендеринга, видаліть непотрібні JS-скрипти і так далі;
  • Витратьте деякий час на те, щоб поекспериментувати з Font Load Events API.
  • Те, що на сторінці використовується один або кілька веб-шрифтів, зовсім не означає, що вона буде завантажуватися повільніше. Добре оптимізований сайт може працювати швидше і краще з використанням веб-шрифтів.

    Переклад статті «Optimizing Web Font Rendering Performance» був підготовлений дружною командою проекту Сайтостроение від А до Я.