Как предотвратить снижение производительности при использовании подключаемых шрифтов

На повестке дня: 1) подключаемые шрифты шикарны, и мы хотим их использовать 2) подключаемые шрифты замедляют загрузку страниц, так как они довольно тяжелые. В последнее время все активно занимаются поиском оптимального решения для использования подключаемых шрифтов, и я решил подготовить сводку некоторых соображений по этому поводу и добавить свои.

Загрузка только для больших экранов

Первым на глаза мне попалось исследование Дейва Руперта (Dave Rupert) по загрузке @font-face только для больших экранов. Оказывается, что если вы используете @font-face, но не применяете прописанную в нем гарнитуру, шрифт закачиваться не будет. Хитро. Демо Дейва.

CSS

@font-face {
	font-family: 'Dr Sugiyama';
	font-style: normal;
	font-weight: 400;
 	src: local("Dr Sugiyama Regular"),
 		 local("DrSugiyama-Regular"),
 		 url(http://themes.googleusercontent.com/static/fonts/drsugiyama/v2/rq_8251Ifx6dE1Mq7bUM6brIa-7acMAeDBVuclsi6Gc.woff) format("woff");
}

body {
	font-family: sans-serif;
}

@media (min-width: 1000px) {
	body {
		font-family: 'Dr Sugiyama', sans-serif;
	}
}

В своей статье «Резервные шрифты для мобильных устройств» в блоге Typekit Джордан Мур (Jordan Moore) предлагает похожий подход.

Я применил такой подход в собственном проекте и подготовил два набора шрифтов: «полный» набор, содержащий все типографические стили, которые я изначально собирался использовать, и «облегченный» набор, состоящий из меньшего количества шрифтов (и, соответственно, с существенно меньшим весом). Один из них подключался с помощью JavaScript в зависимости от ширины экрана, исходя из значения наименьшей ключевой точки.

Используя прием Дейва, вам не нужно волноваться по поводу того, что пользователь увидит текст без примененных к нему веб-шрифтов (эффект проблеска), так как в нем используется @font-face, а его понимает любой браузер. Думаю, приём Джордана оставляет большую вероятность появления этого эффекта, так как шрифт грузится после проверки; но можно поступить как Typekit: применив visibility: hidden и убрав его когда шрифт полностью загружен.

Ajax для шрифтов

Если вас беспокоит увеличение задержки рендеринга (не обязательно время до полной загрузки страницы), можете использовать Ajax для получения таблицы стилей, которая содержит @font-face, когда документ уже отображен. Омар Аль Забир (Omar Al Zabir) написал урок о том как это делается. (спасибо Кевину)

JavaScript

$(document).ready(function(){
  $.ajax({
	url: fontFile,
	beforeSend: function ( xhr ) {
	  xhr.overrideMimeType("application/octet-stream");
	},
	success: function(data) {
	  $("<link />", {
		'rel': 'stylesheet'
		'href': 'URL/TO/fonts.css'
	  }).appendTo('head');
	}
  });
});

Кроме того, стоит убедиться, что установлены заголовки, обеспечивающие длительное кэширования файлов шрифтов. Чтобы предотвратить появление эффекта проблеска, нужно присвоить класс элементу <html> (с помощью JavaScript) и использовать его для того, чтобы скрыть с помощью visibility: hidden все, что нужно скрыть, пока загружаются шрифты, а затем удалить его в callback-функции вызванной Ajax по событию success.

Загрузка шрифтов по требованию, загрузка при последующих загрузках страницы после кэширования

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

PHP

// Проверяем наличие cookie, предполагая что шрифты были кэшированы

if (fonts_are_cached) {
  echo "<link rel='stylesheet' href='/URL/TO/fonts.css'>";
}

В интерфейсе мы делаем противоположное. Если cookie отсутствует, мы загружаем шрифты по требованию и затем добавляем cookie.

JavaScript

// Проверяем наличие cookie, предполагая что шрифты были кэшированы

if (!fonts_are_cached) {

  // Не замедляем отрисовку
  $(window).load(function() {

	// Загружаем шрифты
	$.ajax({
	  url: 'URL/TO/font.woff'
	});
	$.ajax({
	  url: 'URL/TO/font.eot'
	});
	// По сути ничего с ними не делаем, просто запрашиваем их чтобы они были добавлены в кеш

	// Добавляем cookie, которое указывает на то, что шрифты кэшированы

  });

}

Ошибки не исключены, так как cookie не является стопроцентной гарантией того, что шрифт добавлен в кеш. Но если задать ему срок действия, например, продолжительностью в один день, шансы на это неплохие. Эффекта проблеска текста здесь не будет, так как или шрифты не грузятся вообще, или грузятся естественным образом с помощью @font-face. Если вы ничего не имеете против эффекта проблеска (т.е. если вы хотите чтобы подключенные шрифты отображались при первой загрузке страницы несмотря ни на что), можете создать элемент <link> и подключить таблицу стилей со шрифтами, вместо того чтобы запрашивать шрифты.

Еще одним вариантом будет помещение data URI версии шрифта в локальное хранилище и вызов его при необходимости. Следует создать элемент <style>, поместить в него код @font-face, используя data URI версию шрифта, и подключить его. По всей видимости, в The Guardian пробуют так делать.

@tkadlec @davatron5000 @chriscoyier …Дайте мне знать, если заметите ошибки. Пока вроде работает хорошо, хоть это и не лучший вариант. - Scott Jehl @scottjehl

@scottjehl По теме кеша: the Guardian экспериментирует с localStorage. https://github.com/guardian/frontend/blob/master/common/app/assets/javascripts/modules/fonts.js#L88 … @davatron5000 @chriscoyier - Tim Kadlec @tkadlec

Верно подмечено, localStorage может быть более медленным, чем кэширование.

@scottjehl По теме кеша: the Guardian экспериментирует с localStorage. https://github.com/guardian/frontend/blob/master/common/app/assets/javascripts/modules/fonts.js#L88 … @davatron5000 @chriscoyier - Tim Kadlec @tkadlec

@tkadlec @scottjehl @davatron5000 @chriscoyier вариант с локальным хранилищем работает, если оно вам совсем уж необходимо, но оно может читаться медленнее, чем кеш браузера.

Возможное преимущество использования JavaScript - это информация о том, какие версии шрифтов вам нужны.

@StuRobson @chriscoyier Не знаю. @scottjehl, кэшируется хорошо? - Dave Rupert @davatron5000

@davatron5000 @StuRobson @chriscoyier мы подгружаем шрифты в data uri с помощью insertBefore. По умолчанию WOFF.css, на android/ie используются TTF или EOT. - Scott Jehl @scottjehl

И вот как

Перспективы

Чем больше информации у нас есть о пользователях, тем лучше.

Какие у них скорость доступа и задержки? Их довольно сложно определить, и результаты не слишком надежны, даже когда это возможно. Возможно, в один прекрасный день The Network Information API с этим нам поможет.

Какой у них размер экрана? Какими возможностями обладает их браузер? Это можно определить с помощью JavaScript, но вот бы было возможно определять их на серверной стороне? Возможно, в один прекрасный день мы будем использовать для этого Client-Hints.