|
||||
|
Глава 4. Уменьшение числа запросов 4.1. Объединение HTML- и CSS-файлов Число запросов является наиболее узким местом при загрузке страницы. По последним исследованиям, порядка 40% времени загрузки уходит только на установление новых соединений с сервером. В этом свете любые методы, позволяющие уменьшить число запросов, выглядят весьма перспективно. Однако каждый такой метод, начиная с простого объединения стилей или скриптов и заканчивая data:URI, достаточно сложен в технологическом плане, поэтому в ряде случаев может просто не окупать затраченного времени. Зачастую cookie выставляются на весь домен или даже на все поддомены, что означает их отправку браузером даже при запросе каждой картинки с вашего домена. В результате 400-байтный ответ с картинкой превратится в 1000 байтов или даже больше, в зависимости от добавленных заголовков cookie. Если на странице у вас много некэшируемых объектов и большие cookie на домен, то стоит рассмотреть возможность вынесения статичных ресурсов на другой домен (например, так поступил Яндекс, расположив статические файлы на домене yandex.net) и убедиться, что cookie там никогда не появятся. В силу накладных расходов на передачу каждого объекта один большой файл загрузится быстрее, чем два более мелких, каждый в два раза меньше первого. Стоит потратить время на то, чтобы привести все вызываемые JavaScript-файлы к одному или двум, равно как и CSS-файлы. Если на вашем сайте их используется больше, попробуйте сделать специальные скрипты для публикации файлов на «боевом» сервере или уменьшите их количество. Если на странице в большом объеме располагаются десятки небольших GIF-файлов (для оформления границ или фона элементов), стоит рассмотреть ее преобразование в более простой CSS-дизайн (который не потребует такого большого числа картинок) и(ли) объединение в несколько больших ресурсных файлов. Для объединения HTML-файлов существует достаточно простое правило по сведению числа фреймов на странице к минимуму (в идеале их вообще не должно быть, ибо каждый фрейм влечет создание нового документа в дереве страницы, что достаточно ресурсоемко). Поэтому давайте рассмотрим, что можно сделать с файлами стилей. CSS-файлы в начале страницы При заботе о производительности веб-страниц мы всегда хотим, чтобы страницы могли быть отрисованы постепенно, чтобы браузер мог отобразить любой контент сразу же, как он у него появится. Это особенно важно для страниц, на которых много текстового содержания, и для пользователей с медленным подключением. Важность визуального оповещения пользователя о текущем состоянии загрузки страницы каким-нибудь индикатором прогресса детально изучена и задокументирована. Однако в любом случае всегда лучше, если в роли индикатора прогресса выступает сама страница. Когда браузер загружает HTML-файл постепенно — сначала заголовок, потом навигацию, логотип наверху и т. д. — все это служит отличным индикатором загрузки для пользователя. Также это улучшает общее впечатление от сайта. Размещение CSS в конце страницы не позволяет начать постепенное отображение многим браузерам, в числе которых находится и Internet Explorer. Браузер не начинает визуализировать страницу, чтобы не пришлось перерисовывать элементы, у которых во время загрузки изменится стиль. Firefox начинает сразу отрисовывать страницу, в процессе загрузки, возможно, перерисовывая некоторые элементы по мере изменения их свойств, но это является причиной появления нестилизованного контента и рекурсивного его обновления. Спецификация HTML 4 устанавливает, что таблицы стилей должны быть включены в head документа: «В отличие от <a>, <link> может появляться только в секции <head>, зато там он может встречаться сколько угодно раз». Ни одна из альтернатив — белый экран или показ нестилизованного контента — не стоит этого риска (хотя разработчики Firefox и Opera думают несколько иначе). Оптимальным решением является следование спецификации и включение CSS в head-секцию документа. При проектировании небольших сайтов либо несложных дизайнов это правило является основным для максимизации производительности. Однако далее в этой главе мы рассмотрим и альтернативную его трактовку. Объединение CSS-файлов Зачастую на странице подключается несколько файлов стилей: это может быть связано как с модульной структурой построения CSS, так и с поддержкой различных устройств просмотра веб-страниц. Давайте рассмотрим последний случай: у нас есть два вызова CSS-файлов на странице, например: <link type="text/css" rel="stylesheet" href="screen.css" media="screen" /> <link type="text/css" rel="stylesheet" href="print.css" media="print" /> где первый используется для отображения страницы на экране монитора, а второй — для предварительного просмотра и печати. Проблема в том, что браузер не отображает любую часть страницы (это не касается Opera: у нее время отображения страницы без полной загрузке файлов стилей задано по умолчанию в настройках, посмотреть их можно следующим образом: 'preferences' (ctrl-f12) -> 'advanced' -> 'browsing' -> 'loading' или 'инструменты' -> 'настройки' -> 'дополнительно' -> 'перемещение' -> 'загрузка'), пока не загрузит все файлы стилей — в том числе и те из них, которые не предназначены для устройства, с помощью которого производится отображение страницы. Другими словами, браузер не покажет страницу, пока не загрузит и файл стилей для принтера, хотя он совсем и не требуется для визуализации страницы. Это неправильно с точки зрения производительности, но это так (Safari, на самом деле, ведет себя как раз «правильно»: ненужные файлы не задерживают загрузку, но это связано с особенностью модели визуализации; об этом более подробно рассказывается ниже). Практическое решение Решение выглядит весьма тривиально: мы можем в общем CSS-файле объявить правила для любого устройства через @media. Например, все стили для принтера могут быть записаны в следующем виде: @media print { стилевые правила для принтера } в конце основного файла стилей. Таким образом, будет загружаться всегда только один файл. Данное решение может быть легко автоматизировано, и некоторые CMS уже применяют этот подход (в частности, Drupal). Если у нас CSS-файлы разбиты на модули, то нужно пересмотреть их структуру таким образом, чтобы на каждую страницу приходилось не более двух файлов (небольшие файлы — порядка 5 Кб — можно объединить в один для целого раздела). Для главной страницы я рекомендую всегда ограничиться только одним файлом либо вообще включать его в HTML-код (как сделано, например, для главной страницы Яндекса). Два слова об условных комментариях Очень часто верстка страниц производится таким образом, что у нас появляется основной файл стилей и несколько дополнительных, рассчитанных на конкретные браузеры (речь идет в основном об Internet Explorer, однако иногда требуются какие-то специальные правила для Firefox, Opera или Safari). В этом случае файлы подключают через так называемые «условные комментарии», которые выглядят как обычные HTML-комментарии для всех браузеров, кроме Internet Explorer (у остальных браузеров есть свои способы загрузить какой-то файл стилей только для них). Финальная конструкция выглядит примерно следующим образом: <link type="text/css" rel="stylesheet" href="main.css" media="screen" /> <!-—[if lt IE 7]><link type="text/css" rel="stylesheet" href="ie6.css" media="screen" /><!endif--> Для всех браузеров используется main.css, а для IE6 и ниже — ie6.css. Однако Internet Explorer этих версий не запрашивает файлы стилей параллельно, поэтому при загрузке страницы произойдет ненужная задержка, связанная с доставкой еще одного файла. Чтобы избежать ее (особенно в случае небольшого количества стилей конкретно для IE), можно использовать CSS-хаки уже в исходном CSS-файле. Например, если нам нужно определить правило только для IE7, мы можем написать так: *+html body { margin: 0 auto; } для IE6: * html body { margin: 0 auto; } и для IE5.5- (эта группа браузеров не распознает экранирование символов, поэтому сможет применить только первое правило из двух, второе правило отработает для IE6, переопределив первое): * html body { margin: 0; marg\in: 0 auto; } CSS-хаки позволяют совершенно свободно использовать всего один файл стилей для гарантии кроссбраузерности верстки. При этом производительность страницы будет максимальной (исключая, конечно, случай включения всех CSS-правил в исходный HTML-документ — это будет работать еще быстрее, однако чревато некоторыми сложностями; подробнее о них в конце этой главы). 4.2. Объединение JavaScript-файлов Все внешние JavaScript-файлы с сайта можно слить в один большой, загружаемый только один раз и навсегда. Это очень удобно: браузер не делает тысячу запросов на сервер для отображения одной страницы, скорость загрузки резко повышается. А пользователи так же счастливы, как и разработчики.Как всегда, в бочке меда есть ложка дегтя: в объединенный файл попадает много того, что при первом запросе можно было бы и не загружать. Чаще всего для борьбы с этим предлагают ненужные части убирать вручную. Однако каждый раз делать одни и те же операции после изменения модулей очень надоедает. Ниже приведено описание простейшего алгоритма разрешения этой проблемы путем описания зависимостей между модулями.Конструктивные предложения Для начала стоит разобрать используемый фреймворк на составные части. JSON — отдельно, AJAX — отдельно, работа с DOM — отдельно, формы — отдельно. После этого задача «выкидывания ненужного» превращается в задачу «собери только нужное». Несомненный плюс — результат сборки стал гораздо меньше. Несомненный минус — если что-то из «нужного» забыто, все перестает работать.Информацию о зависимостях между составными частями можно хранить в удобном для автоматического использования виде. (Формы используют функции DOM, JSON — AJAX и так далее.) На этом шаге забыть что-то нужное становится заметно труднее, а сборка превращается из увлекательной головоломки в рутинную и автоматизируемую операцию.Также можно хранить информацию о том, какие именно модули нужны сайту в целом. Используется ли AJAX? Если ли формы? Может быть, какие-то необычные элементы управления?Да, естественно, все это можно и нужно автоматизировать.В теории С формальной точки зрения, после того как первые два предложения воплощены в жизнь, у нас появляется дерево зависимостей. Например, такое:— dom.js — array.map.js — array.js — sprinf.js — calendar.js — date.js — mycoolcombobox.js — dom.js — array.map.js — array.js — sprinf.js — animated.pane.js — pane.js — dom.js — array.map.js — array.js — sprinf.js — animation.js — transition.js ... и так далее ... Дальше мы выбираем непосредственно нужные сайту вершины. Пусть это будут dom.js и animated.pane.js. Теперь это дело техники — обойти получившийся набор деревьев в глубину:— array.js — array.map.js — sprinf.js — dom.js — array.js — array.map.js — sprinf.js — dom.js — pane.js — transition.js — animation.js — animated.pane.js ...удалить повторяющиеся элементы:— array.js — array.map.js — sprinf.js — dom.js — pane.js — transition.js — animation.js — animated.pane.js и слить соответствующие модули воедино.На практике Хранить информацию о зависимостях можно, например, следующим образом (добавляя в «модули» служебные комментарии):// #REQUIRE: array.map.js // #REQUIRE: sprintf.js .... код Выделить подобные метки из текстового файла не составляет труда. Естественно, чтобы получить полное дерево зависимостей, надо будет пройтись по всем доступных файлам — но полное дерево обычно не нужно.К чему мы пришли? Затратив один раз кучу времени на формирование модулей и зависимостей между ними, мы экономим время каждый раз, когда хотим уменьшить объем загружаемого клиентов внешнего файла. Это приятно. Но все-таки часть проблемы осталась — пользователь загружает весь JavaScript-код, который используется на сайте в течение первого захода на произвольную страницу, даже если на текущей странице этот код не нужен.Итак, мы оставили нового пользователя наедине с единственным JavaScript-файлом, не включающим ничего лишнего. Стал ли при этом пользователь счастливее? Ничуть. Наоборот, в среднем пользователь стал более несчастным, чем раньше, а причина этому — увеличившееся время загрузки страницы.Немного из теории HTTP-запросов Время загрузки ресурса через HTTP-соединение складывается из следующих основных элементов:время отсылки запроса на сервер T1 — для большинства запросов величина практически постоянная; время формирования ответа сервера — для статических ресурсов, которые мы сейчас и рассматриваем, пренебрежимо мало; время получения ответа сервера T2, которое, в свою очередь, состоит из постоянной для сервера сетевой задержки L и времени получения ответа R, прямо пропорционального размеру ресурса. Итак, время загрузки страницы будет состоять из времени загрузки HTML-кода и всех внешних ресурсов: изображений, CSS- и JavaScript-файлов. Основная проблема в том, что CSS и JavaSscript-файлы загружаются последовательно (разработчики браузеров уже работают над решением этой проблемы в последних версиях, однако пока еще 99% пользователей страдают от последовательной загрузки). В этом случае общение с сервером выглядит так:— запросили страницу — получили HTML — запросили ресурс A: T1 — получили ресурс A: L + R(A) — запросили ресурс B: T1 — получили ресурс B: L + R(B) — запросили ресурс C: T1 — получили ресурс C: L + R(C) Общие временные затраты при этом составят 3(T1+L) + R(A+B+C).Объединяя файлы, мы уменьшаем количество запросов на сервер:— запросили страницу — получили HTML — запросили ресурс A+B+C: T1 — получили ресурс A+B+C: L + R(A + B + C) Очевидна экономия в 2(T1 + L).Для 20 ресурсов эта экономия составит уже 19(T1 + L). Если взять достаточно типичные сейчас для домашнего/офисного Интернета значения скорости в 256 Кбит/с и пинга ~20-30 мс, получим экономию в 950 мс — одну секунду загрузки страницы. У людей же, пользующихся мобильным или спутниковым интернетом с пингом более 300 мс, разница времен загрузки страниц составит 6-7 секунд.На первый взгляд, теория говорит, что загрузка страниц должна стать быстрее. В чем же она разошлась с практикой?Суровая реальность Пусть у нашего сайта есть три страницы — P1, P2 и P3, поочередно запрашиваемые новым пользователем. P1 использует ресурсы A, B и C, P2 — A, С и D, а P3 — A, С, E и F. Если ресурсы не объединять, получаем следующее:P1 — тратим время на загрузку A, B и C P2 — тратим время на загрузку только D P3 — тратим время на загрузку E и F Если мы слили воедино абсолютно все JavaScript-модули сайта, получаем:P1 — тратим время на загрузку (A+B+C+D+E+F) P2 — внешние ресурсы не требуются P3 — внешние ресурсы не требуются Результатом становится увеличение времени загрузки самой первой страницы, на которую попадает пользователь. При типовых значениях скорости/пинга мы начинаем проигрывать уже при дополнительном объеме загрузки в 23 Кб.Если мы объединили только модули, необходимые для текущей страницы, получаем следующее:P1 — тратим время на загрузку (A+B+C) P2 — тратим время на загрузку (A+C+D) P3 — тратим время на загрузку (A+С+E+F) Каждая отдельно взятая страница при пустом кэше будет загружаться быстрее, но все они вместе — медленнее, чем в исходном случае. Получаем, что слепое использование модного сейчас объединения ресурсов часто только ухудшает жизнь пользователя.Возможное решение Конечно же, выход из сложившегося положения есть. В большинстве случаев для получения реального выигрыша достаточно выделить «ядро» — набор модулей, используемых на всех (или, по крайней мере, на часто загружаемых) страницах сайта. Например, в нашем примере достаточно выделить в ядро ресурсы A и B, чтобы получить преимущество:P1 — тратим время на загрузку (A + B) и C P2 — тратим время на загрузку D P3 — тратим время на загрузку (E + F) Вдумчивый читатель сейчас возмутится и спросит: «А что, если ядра нет? Или ядро получается слишком маленьким?». Ответ: это легко решается вручную выделением 2-3 независимых групп со своими собственными ядрами. При желании задачу разбиения можно формализовать и получить точное машинное решение — но это обычно не нужно; руководствуясь простейшим правилом — чем больше ядро, тем лучше, — можно добиться вполне приличного результата.Реализация на PHP После разделения JavaScript- и CSS-кода по файлам для поддержания модульной структуры можно в контроллере создать список файлов, которые надо присоединить к данному документу (вместо того чтобы прописывать это вручную в шаблоне отображения). Но теперь надо сделать так, чтобы до показа шаблона вызывалась функция кэширования, которая проходилась бы по списку, проверяла из него локальные файлы на время изменения, объединяла в один файл и создавала или перезаписывала gz-файл с именем, сформированным из md5-хэша имен входящих файлов. В качестве рабочего примера можно привести следующую функцию: function cache_js(){ $arrNewJS=array(); $strHash=''; $strGzipContent=''; $intLastModified=0; // проходимся по списку файлов foreach ((array)$this->scripts as $file){ if (substr($file,0,5)=='http:') continue; if ($file[0]=='/') $strFilename=sys_root.$file; else $strFilename=sys_root.'app/front/view/'.$file; $strHash.=$file; // читаем содержимое в одну строку $strGzipContent.=file_get_contents($strFilename); $intLastModified=$intLastModified<filemtime($strFilename) ? filemtime($strFilename) : $intLastModified; } $strGzipHash=md5($strHash); $strGzipFile=sys_root.'app/front/view/js/bin/'.$strGzipHash.'.gz'; // проверяем, надо ли перезаписать gz-файл if (file_exists($strGzipFile) && $intLastModified>filemtime($strGzipFile) || !file_exists($strGzipFile)){ if (!file_exists($strGzipFile)) touch($strGzipFile); // используем функции встроенной в php библиотеки zlib для архивации $gz = gzopen($strGzipFile,'w9'); gzputs ($gz, $strGzipContent); gzclose($gz); } // перезаписываем список на один файл $arrNewJS[]='js/bin/'.$strGzipHash.'.gz'; $this->scripts=$arrNewJS; } Для CSS основные теоретические моменты описаны выше, а реализация даже несколько проще. Если использовать YUI Compressor, то решение будет совершенно одинаково (вычислили зависимости, склеили файлы, сжали, переименовали, сделали архив) для обоих типов файлов. PHP Speedy К сожалению, почти все описанные выше методы применимы только на стадии разработки или существенной оптимизации и требуют участия опытных разработчиков для своей интеграции. Но возникает резонный вопрос: может быть, уже существуют какие-либо автоматизированные решения для автоматического объединения CSS- или JavaScript-файлов? И что делать, если хочется ускорить сайт на существующей платформе одной из популярных CMS, где в коде уже «сам черт ногу сломит»? В таких случаях можно использовать проект с открытым кодом PHP Speedy ( http://aciddrop.com/php-speedy/ ) — РНР-скрипт, который обеспечивает расширенное кэширование и сжатие компонентов страницы, не требуя никаких модификаций вручную. Достаточно только установить скрипт на сервере и сделать несложные настройки, заключающиеся в указании директории с самим сайтом и папок для кэширования файлов. Скрипт умеет автоматически склеивать все CSS- и JavaScript-файлы, кэшировать их, применяет оптимизацию (с помощью пакета Minify, http://code.google.com/p/minify/ , о котором уже шла речь выше), а также gzip-сжатие. На выходе мы получаем автоматическую оптимизацию сайта совершенно бесплатно. Хотя, конечно, не следует рассматривать это как конец — для начала такое решение вполне приемлемо, однако, для достижения максимальной производительности сайта придется со временем все больше и больше настраивать некоторые моменты вручную и применять советы из этой книги. Для владельцев блогов на популярном движке Wordpress есть специальное расширение на основе этого решения, добавляющее оптимизацию буквально одним кликом в панели управления. Решение очень даже полезное: блоги часто расположены на слабых хостинговых площадках, а популярность проекта может возрасти буквально после пары публикаций. Площадка может быть к этому просто не готова, да и сам Wordpress не является вершиной оптимизации с точки зрения нагрузки. 4.3. Техника CSS Sprites Рассмотрев все аспекты объединения текстовых файлов, перейдем к графической и мультимедийной информации. Сейчас уже много где написано и упомянуто про технику CSS Sprites (или CSS Image Maps). Ниже приведены несколько примеров и полезных ссылок. И пара советов, где и как этот метод может быть применим наиболее оптимальным образом. Сама техника заключается в том, что мы создаем комбинированное изображение, из которого затем «вырезаем» с помощью CSS-свойства background-position нужный нам в данном случае кусок. На текущем уровне поддержки браузерами (порядка 99,9%) она является просто обязательной для любого уважающего себя веб-ресурса, так как позволяет сократить число запросов к серверу — а кроме этого отделить поведение от представления, и возложить труд по анимации на CSS-движок браузера, а не на JavaScript-движок (т. е. это будет работать даже с выключенными скриптами), и много-много прочих «вкусностей». Но обо всем по порядку. Простой rollover-эффект Обычно таким термином называют смену графической картинки при наведении на нее мыши — своеобразный призыв к действию (как его любят называть маркетологи). У веб-мастеров сложилась дурная практика делать такие эффекты через onmouseover/onmouseout на картинках. Это прямое нарушения принципа разделение представления от поведения и несемантическая (в лучшем случае, в худшем — еще и невалидная) верстка. И вообще, это очень плохо. В данном случае это делается средствами CSS и является семантически правильным (в большинстве HTML-документов — это ссылка, но с элементами форм приходится немного повозиться, однако тоже ничего сверхъестественного). Пример: при наведении просто показывается другая картинка. Рис. 4.1. Пример фонового изображения для простого rollover-эффекта. Источник: www.websiteoptimization.comСоответствующая часть в CSS-коде будет выглядеть примерно так: a.sprited { background: yellow url(http://site.ru/img/button.png) 0 0 no-repeat; width: 100px; height: 20px; } a.sprited:hover { background-position: -100px 0; background-color: red; } При наведении на ссылку фоновое изображение под ней «уедет», и покажется правая его часть (ибо она изначально скрыта жесткими размерами самой кнопки). Дополнительно мы обеспечили обратную совместимость для тех пользователей, у которых отключены картинки. Для них мы меняем цвет фона при наведении (если картинки в браузере включены, то изображение покажется поверх цвета). Сложный rollover-эффект Под таким термином стоит понимать те случаи, когда в одном файле содержится несколько «динамических кнопок». Например, это может быть такое изображение: Рис. 4.2. Пример фонового изображения для сложного rollover-эффекта. Источник: www.spegele.comПроблемные места в IE В некоторых версиях IE изменение расположения фона при наведении мыши отрабатывает не совсем корректно, и браузер запрашивает исходную фоновую картинку с сервера еще раз, что вызывает «мигание» картинки. Одним из вариантов борьбы с такой проблемой может стать изменение не позиции фонового элемента, а его прозрачности. Например, мы можем сразу задать параметры фона для элемента и его прямого потомка, а при наведении мыши менять фон дочернего элемента на прозрачный, тогда фон родителя будет просто просвечивать: <a href="/"><span>Начало</span></a>" a { background: yellow url(http://site.ru/img/button.png) 0 0 no-repeat; display: block; height: 20px; width: 100px; } a span { background: red url(http://site.ru/img/button.png) -100px 0 no-repeat; display: block; height: 20px; width: 100px; } a:hover span { background: transparent; } К сожалению, этот метод предполагает появление у элемента несемантического потомка для обеспечения графических эффектов. Более стандартным вариантом будет вызов специфичного для IE метода backgroundImageCache (через try или любое другое условие, гарантирующее обратную совместимость с остальными браузерами): try { document.execCommand("BackgroundImageCache", false, true); } catch (e) {} В данном случае мы форсируем кэширование фоновых изображений, что предотвращает описанную выше ошибку. CSS Image map Этот пункт стоит намеренно выделить, ибо он подразумевает более свободное использование ресурсного файла для «подсветки» какого-либо изображения при наведении. Если в предыдущих случаях области были одинакового размера, то тут уже размер областей может быть, вообще говоря, произвольным. Одним из преимуществ такого подхода является совмещение разных областей, чтобы они занимали минимум места. Эта техника как раз и заменила классический Image Map. Рис. 4.3. Пример изображения для CSS Image Map. Источник: www.acronis.comСтатичные картинки Кроме динамических эффектов CSS Sprites широко используется и для объединения статических изображений. Давайте рассмотрим различные плюсы и минусы этого подхода. Основной опасностью склеивания большого количества иконок в одном месте являются артефакты при увеличении шрифта: посторонняя часть фонового изображения проявляется совершенно не в том месте, где его ожидали, и у пользователя возникает ощущение, что страница «разваливается». Блок становится выше или длиннее, запаса полей данного изображения уже не хватает, в результате у одного элемента отображается сразу несколько иконок. Непорядок. Как с ним бороться, будет рассказано немного ниже в общих советах по созданию ресурсных картинок для CSS Sprites. В общем случае нужно жестко ограничивать размеры контейнера, у которого заданы определенные фоновые картинки, чтобы даже при увеличенном тексте картинка не «ломалась» (однако текст может стать нечитаемым из-за обрезания по границе блока). Как бороться с последней напастью, также будет рассказано в конце раздела. В некоторых случаях возможно также объединение всех фоновых изображений на странице в одно-единственное. Такой подход значительно сокращает число запросов к серверу, однако влечет за собой и технологические сложности. Как пример можно привести следующее изображение: Рис. 4.4. Пример фонового изображения «все-в-одном». Источник: webo.inОнлайн-генераторы www.csssprites.com. Обладает довольно минималистичным дизайном, есть возможность загружать несколько исходных файлов. www.printf.ru/spritr/. В этом инструменте есть возможность загружать несколько файлов, очень милый дизайн, но в целом настроек мало. spritegen.website-performance.org. Тут очень много настроек, также можно гибко создавать и сам CSS-фрагмент, но все картинки нужно загружать одним архивом. Полезные советы Итак, самое вкусное. Для начала стоит разбить все фоновые картинки на 5 групп (однако пятая группа может быть объединена с любой из первых четырех). Анимированные картинки Те, которые предполагается повторять по всем направлениям (repeat) Те, которые предполагается повторять по горизонтали (repeat-x) Те, которые предполагается повторять по вертикали (repeat-y) И те, которые предполагается показывать только один раз (no-repeat) Откуда взялось разделение на такие группы? Из очень простых соображений: если картинка будет повторяться по какому-то направлению, то по этому направлению она должна быть одна-единственная в своем «окне», иначе повторяться будет не только она одна. Также стоит ориентироваться на общий размер файла в 10–20 Кб: если файл получается больше, то лучше подключать больше одного (соображения по разбиению файлов по размеру более подробно приведены в пятой главе). Далее, все картинки из второй группы (повторяющиеся по всем направлениям) оставляем как есть (вообще говоря, можно подумать над их преобразованием в стилевые правила для самых простых случаев, но это уже тема для отдельной дискуссии). Все картинки из третьей группы можно склеить по вертикали (тогда в своем горизонтальном окне они будут единственными), все картинки из четвертой группы — по горизонтали. Что же делать с пятой группой? Тут нам нужно понять, для чего будет использоваться каждая картинка. Если она будет изображать фиксированную по размерам кнопку, то ее можно размещать в любом месте итогового ресурсного файла. Если она будет использована как иконка для списка (размещение в левом верхнем углу элемента), то мы должны очистить все пространство правее и ниже ее. Таким образом, при любом увеличении такого элемента (а «растут» элементы у нас всегда вниз и вправо) ничего лишнего не выводилось. В таком случае иконки располагаются не вертикально, а «лесенкой» (из правого верхнего угла в левый нижний). Рис. 4.5. Пример фонового изображения с расположением картинок «лесенкой». Источник: webo.inОписанная выше проблема с изменением размера надписей в фиксированных кнопках (фон у них фиксированный, поэтому мы не можем их раздвигать при увеличении шрифта, и он обрезается) может быть преодолена путем разбиения фона на 4 части (угловые) и задания соответствующего цвета фона для всех элементов. Однако это повлечет наличие как минимум 4-х вложенных элементов для отображения каждого угла. Не во всех случаях это допустимо семантически. Да, можно создавать дополнительную разметку при помощи JavaScript, но насколько оно того будет стоить? Это лучше решать в каждом конкретном случае. Давайте теперь перейдем к методам, позволяющим вставлять графическую информацию непосредственно в текстовые файлы без необходимости запроса внешних ресурсов. 4.4. Картинки в теле страницы с помощью data:URI Встроенные изображения используют схему data:URI для внедрения прямо в тело веб-страницы. Как было определено в RFC 2397, такие URI предназначены для вставки небольших объектов как «непосредственных данных» (которые можно использовать без дополнительных запросов внешних ресурсов). Такие объекты должны рассматриваться так же, как и любые другие внешние файлы. Использование встроенных изображений позволяет сэкономить HTTP-запросы к внешним ресурсам. Поддержка браузерами Хотя Opera 7.2+, Firefox, Safari, Netscape и Mozilla поддерживают data:URI, Internet Explorer 5–7 — решительно нет. Однако Internet Explorer 8 будет поддерживать эту схему. Существует также несколько приемов для поддержки старых версий Internet Explorer (о них чуть ниже). Схема data:URI Схема data:URI предоставляет способ для внедрения «непосредственно данных» точно так же, как если бы они были подключены через вызовы внешних файлов. Синтаксис у нее следующий: data:[<тип данных>][;base64],<данные> В случае простых изображений вам нужно указать mime-тип для них (например, image/gif), за ним идет base64-представление бинарного файла с изображением. Ниже приведен пример (переводы строк добавлены, чтобы не разрывать страницу, на самом деле их нет): <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAIAAAD5gJpuA AAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUKM9tjz1OwlEQxH8P/hV2NIZY0NhYeA0TbkLLPTyFV6DgLhYWFIaOmEhM3szbtXhEPmSy2Z3d2Y9sORySEyKih87iCg4GYDIByEwoQGbPCowzR3mG3e576Jsz85zkLZRSIqIsFrlc5n5PBK1la0Rka2lfeDun07JafQ2bTTw/l+0WOy3klFLKWq/9fA4wADZS/g10ufdVpeqxYheIAehHq9Li1PrvgpQQw5rxk15/6mfYWR1yVIUc0pFUNync7vyw5m14gbHfQx+3l3di4Vba4z0MASOZ2Swl3LCQitQ/w8amtW4B5QBxZlymVxLwCz+JZR4AeSrEAAAAAElFTkSuQmCC" width="16" height="14" alt="внедренная иконка"/> В результате мы получим следующее изображение иконки флага: Рис. 4.6. Пример изображения, вставленного с помощью data:URI. Источник webo.inCSS и встроенные изображения Такие изображения, внедренные в HTML-страницы, не кэшируются для повторного использования. И они не кэшируются от страницы к странице (это логично: ведь нам нужно каждый раз загружать HTML-код для отображения этой картинки, они будут кэшироваться только с HTML, их содержащим). Однако CSS-файлы замечательно кэшируются браузерами, и такие изображения могут быть повторно применены вместе с использующим их селектором, например: ul { list-style: none; } ul li { margin: 0 0 1px; background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAIAAAD5gJpuAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUKM9tjz1OwlEQxH8P/hV2NIZY0NhYeA0TbkLLPTyFV6DgLhYWFIaOmEhM3szbtXhEPmSy2Z3d2Y9sORySEyKih87iCg4GYDIByEwoQGbPCowzR3mG3e576Jsz85zkLZRSIqIsFrlc5n5PBK1la0Rka2lfeDun07JafQ2bTTw/l+0WOy3klFLKWq/9fA4wADZS/g10ufdVpeqxYheIAehHq9Li1PrvgpQQw5rxk15/6mfYWR1yVIUc0pFUNync7vyw5m14gbHfQx+3l3di4Vba4z0MASOZ2Swl3LCQitQ/w8amtW4B5QBxZlymVxLwCz+JZR4AeSrEAAAAAElFTkSuQmCC) 0 0 no-repeat;) height: 14px; text-indent: 10px; } Проблемы data:URI С описанным выше подходом для подключения изображений связаны две основные проблемы. Во-первых, вам нужно пересчитывать base64-представление изображений и редактировать CSS-файл каждый раз, когда само изображение меняется. Также IE до версии 7 включительно не поддерживает встроенных изображений. У первой проблемы есть простое решение на PHP: <?php echo base64_encode(file_get_contents("../images/flag.png")) ?> Этот код читает файл с изображением и автоматически преобразовывает его на сервере в base64. Однако простота этого решения повлечет некоторую дополнительную нагрузку на сервер. Как вариант, можно рассмотреть автоматический пересчет всех картинок и вставку их в CSS-файл, например, раз в 5 минут по необходимости (если файл с изображением изменился). Дополнительно нужно будет озаботиться тем, чтобы сбросить кэширование для самого CSS-файла, содержащего такие изображения. Работа в Internet Explorer Существует три способа обойти отсутствие в IE поддержки data:URI. Используя распознавание браузеров (например, с помощью условных комментариев, ведь речь идет только про IE), можно просто отображать внешнее изображение для IE и встроенные изображения для остальных браузеров. Или вы можете применить JavaScript для эмуляции этой поддержки в IE, но эта техника потребует довольно значительного объема JavaScript-кода. О третьем способе пойдет речь в разделе, описывающем mhtml-технику. Вышеприведенный PHP-код позволяет легко вставить base64-аналог изображения (можно расширить этот пример, чтобы, например, распознавать заголовки, отправляемые браузером серверу, и только для IE выводить URL для изображения, для остальных же кодировать его в base64): ul { list-style: none; } ul li { margin: 0 0 1px; background: url(data:image/gif;base64,<?php echo base64_encode(file_get_contents("../images/flag.png")) ?>) top left no-repeat; height: 14px; text-indent: 10px; } Когда сервер начнет анализировать CSS-файл, он автоматически перекодирует бинарный файл изображения в base64 и отправит эти данные внутри CSS-файла. Следующим шагом будет добавление распознавания браузеров для отправки изображения только IE и встроенных изображений всем остальным. Это можно сделать либо внутри CSS-файла с PHP-кодом, либо с помощью условных комментариев, например: <!--[if gte IE 5]> <style type="text/css" src="ie.css"> <![endif]--> <!--[if !(IE)]> <style type="text/css" src="main.css"> <![endif]--> В файле ie.css должно быть нормальное обращение к картинке, например: ul li { margin: 0 0 1px; background: url(/images/flag.png) 0 0 no-repeat; } ... Преимущества и недостатки data:URI Вместе с техникой CSS Sprites (или как ее альтернатива) data:URI может существенно уменьшить число HTTP-запросов. Краткий список плюсов данного метода: Экономят HTTP-запросы, предотвращают издержки, связанные с большим числом объектов. Экономят число параллельных потоков: у браузеров есть ограничение (по спецификации HTTP/1.1, однако Firefox, Opera и Safari несколько вольно его трактуют, в частности, позволяя настраивать этот параметр или значительно его увеличивая; о настройках браузеров можно прочитать в восьмой главе) на число одновременных соединений (подробнее об этом рассказывается в пятой главе) с одним хостом. Упрощают HTTPS-запросы и улучшают производительность при таком типе соединения. Однако встроенные изображения (только data:URI) не поддерживаются в Internet Explorer 5-7. Текстовое base64-представление данных также занимает больше, чем бинарное изображение. В наших тестах base64-данные были на 39-45% больше бинарного аналога, но gzip-сжатие позволяет уменьшить разницу до 5-10%. Предварительная оптимизация изображений перед base64-кодированием позволяет уменьшить их размер пропорционально (о сжатии изображений было рассказано во второй главе). Также существует ряд ограничений на размер встроенных изображений. От браузеров требуется поддерживать только URL длиной до 1024 байтов, в соответствии с вышеупомянутой спецификацией RFC. Однако браузеры более либеральны к пользователям в том, что они принимают. Например, Opera и Firefox последних версий поддерживают data:URI примерно до 50 Кб (для IE8 этот предел составляет 32 Кб). Но все же эта техника подходит больше для небольших по размеру изображений. Краткий список минусов: Не поддерживается IE до версии 7 включительно. Требуются дополнительные действия для обновления внедренного содержания (перекодировать, еще раз вставить). Ограничена длина. Не подходит для вставки больших изображений. Изображения, представленные в base64-кодировке, примерно на 33% больше размера их бинарного аналога (на 10% — при использовании сжатия). Встроенные картинки (не в CSS) не получится закэшировать по определению. Они будут кэшироваться только с HTML-кодом. Дополнительные соображения по оптимизации Одним из возможных выходов из сложившейся ситуации будет использование характерных для IE CSS-хаков, чтобы только для него подключить фоновые изображения. В итоге вышеприведенный пример будет выглядеть примерно так: ul { list-style: none; } ul li { margin: 0 0 1px; background: url(data:image/gif;base64,<?php echo base64_encode(file_get_contents("../images/flag.png ")) ?>) top left no-repeat; height: 14px; text-indent: 10px; } * html ul li { background-image: url(/images/flag.png); } *+html ul li { background-image: url(/images/flag.png); } Также возможно кодирование изображений, которые выводятся в base64, автоматически при изменении этих изображений (для этого потребуется простой скрипт, который проверяет, обновились ли соответствующие файлы; если обновились, то перезаписывает их представление в CSS-файле, заодно и меняет хэш-строку для подключения этого файла в HTML, чтобы избежать кэширования). Для включения небольших графиков прямо в HTML-код прекрасно подойдут условные комментарии, когда для ряда браузеров изображение выводится в base64, а для остальных (например, для IE) подключается через условные комментарии. Например, так: <!--[if !IE]>--> <img src="data:image/png;base64,..." alt="График" title="График"/> <!--<![endif]--> <!--[if IE]> <img src="chart.png" alt="График" title="График"/> <![endif]--> Если использовать связку «относительное позиционирование родителя — абсолютное позиционирование дочернего элемента», то IE будет просто выводить картинку из внешнего файла поверх непонятного (для него) объекта. Кроссбраузерное использование data:URI IE (до версии 7 включительно) не поддерживает протокол data:URI, а вместе с ним base64-кодирование внешних файлов и включение их прямо в тело необходимого документа (будь то HTML или CSS/JavaScript-файл). Однако если рассмотреть использование протокола mhtml (который, конечно же, аккуратно поддерживается только в IE), многое становится более ясным, и base64-кодирование удается применять в полной мере. О, этот странный Microsoft! В IE существует альтернативное решение для вставки изображений прямо в текстовый документ в виде mhtml-включений. Давайте остановимся на его практической реализации для CSS-файлов (в данном случае это файл main.css): /* Content-Type: multipart/related; boundary="_" --_ Content-Location: 1 Content-Transfer-Encoding: base64 iVBOR.. */ Далее в CSS-файле нужно лишь вызвать эту картинку следующим образом: ul li { background-image:url(mhtml:http://site.ru/main.css?20080531!1); } Здесь в адресе картинки идет протокол mhtml (который поддерживается исключительно в IE, но это не так важно), далее полный URL до CSS-файла (который содержит эту картинку), в данном случае этот URL еще и содержит GET-параметр для соответствующего кэширования. И небольшая тонкость применения данного формата: необходимо в URL использовать ту же строку, что и в HTML-файле, в котором подключается данный CSS, иначе IE запросит CSS-файл дважды: первый раз как таблицу стилей, второй раз — как хранилище картинки. Далее после восклицательного знака (!) идет тот идентификатор, который мы назначили картинке в Content-Location. И все. Объединяем несовместимое С одной стороны, у нас схема data:URI, которая поддержана W3C и распознается всеми браузерами, кроме IE. С другой стороны, у нас IE, который понимает mhtml и с которым работают 70% наших пользователей. Мы объединим эти два решения, благо они оба используют base64-представление картинок. Задача первая: объединить оба назначения стилевых правил, чтобы они не конфликтовали друг с другом. Решается это очень просто с помощью либо отдельного CSS-файла для IE (через условные комментарии), либо CSS-хаков (последнее предпочтительнее, ибо позволяет загружать всего один CSS-файл). В итоге в CSS-файле мы имеем примерно следующее: /* Content-Type: multipart/related; boundary="_" --_ Content-Location: 1 Content-Transfer-Encoding: base64 iVBOR.. */ ul li { background: #fff url(data:image/png;base64,iVBOR...) 0 0 no-repeat; } * html ul li { background-image: url(mhtml:http://site.ru/main.css?20081010!1); } *+html ul li { background-image: url(mhtml:http://site.ru/main.css?20081010!1); } Данная конструкция позволяет вывести фоновое изображение в base64-кодировке для всех (ну или 99,9%) браузеров. Почему в конце содержатся 2 разных CSS-селектора с одним объявлением? Первое предназначено для IE6 и предыдущих версий, второе — для IE7. Объединить через запятую их нельзя. Панацея или ящик Пандоры? Первый же скептический вопрос, который каждый читатель должен в этом месте задать: как можно эти картинки выводить только один раз в CSS-файле (можно заметить, что base64-строка фигурирует там дважды)? Ответ: никак. Однако выход есть. Это gzip-сжатие. Если очень коротко, то при любом архивировании создается некоторый словарь наиболее часто используемых строк, которые заменяются на однобайтовые представления. При восстановлении данных из архива происходит обратная процедура, когда значения слов словаря подставляются вместо их наименований. В нашем случае у нас для одной картинки используется одна и та же base64-строка. Это означает, что при архивировании она сожмется удивительно эффективно. Стоит также заметить, что при таком подходе результирующий сжатый файл занимает даже меньше, чем при назначении с помощью CSS-хаков для IE внешних изображений для загрузки (ведь используется меньше различных конструкций). Валидность Получившийся таким образом CSS-файл абсолютно валиден (так как все mhtml-вставки происходят в комментариях). Каждое CSS-объявление валидно с точки зрения CSS 2.1, и те небольшие приемы, которые позволяют эффективно сжать данные для всех браузеров, не отражаются на восприятии ими файла. Такой подход будет уместным только для CSS. Для HTML ситуация совсем другая (о ней немного подробнее ниже). HTML-документ при таком подходе оказывается невалидным, что выливается во множественные проблемы верстки, и это не та же цена, которую можно заплатить за некоторый не очень явный рост производительности. Некоторые итоги Картинки в CSS можно вставлять с помощью data:URI. Для IE можно использовать mhtml, полностью дублирующий эту функциональность. Для корректного применения стилей нужны CSS-хаки либо разделение CSS-файлов. Используем gzip для CSS-файлов для устранения последствий множественного использования base64-строки. Использовать base64-кодирование на ваших страницах можно уже прямо сегодня. Очень важно при этом понимать возможные последствия и по возможности обойтись малой кровью. Однако с выходом IE8 и массовым его распространением ситуация кардинально изменится (ведь он поддерживает data:URI и для него описанные обходные приемы уже теряют свою актуальность). Включение музыки (base64) Летом 2008 года весь мир облетела страница, содержащая реализацию первого уровня Super Mario Brothers. В нее, в общем, можно играть, хотя упущены многие ключевые аспекты (нет грибов, нет флага, нет повышающих очков и т. д.). Однако это, на самом деле, не самый интересный аспект этой разработки. Наверное, больше всего интереса в этом веб-приложении представляет не сама игровая механика, а тот факт, что все собрано в одном-единственном файле — игровом скрипте, который включает все игровые спрайты и всю музыку. Это весьма любопытный образец использования технологий. Удивительно, но способ подключения музыки в этом приложении, пожалуй, наиболее простой для понимания. Приложение использует схему data:URI, которая кодирует музыкальные MIDI-файлы Mario в base64-виде. Результат очень просто найти в исходном файле: aSounds = [ // очень маленькая и простая тема Mario. Написал Mike Martel. "data:audio/mid;base64,TVRoZAAAAAYAAQAEAMBNVH...", // игра закончилась. Написал John N. Engelmann. "data:audio/mid;base64,TVRoZAAAAAYAAQADAHhNVH..." ], data:URI работает следующим образом: все содержимое выбранного файла кодируется в виде одной строки в исходном файле. В результате мы получаем 1 файл вместо трех: одного исходного и двух внешних. В данном случае вся закодированная строка отправляется элементу <embed/>, который и отвечает за проигрывание MIDI-файла. Можно обнаружить конечный результат этих махинаций в следующем виде: playMusic = function(iSoundID, bLoop) { if (!bMusic) return; var oEmbed = dc("embed"); oEmbed.src = aSounds[iSoundID]; oEmbed.id = "sound_" + iSoundID; if (bLoop) oEmbed.setAttribute("loop", "true"); oEmbed.setAttribute("autostart", "true"); oEmbed.style.position = "absolute"; oEmbed.style.left = -1000; appChild(document.body, oEmbed); }, Вышеприведенный код просто создает элемент embed, устанавливает для него автозапуск проигрывания музыки и в качестве источника данных указывает data:URI. В результате мы получаем MIDI-файл, который сам начинает проигрываться (предположительно, тот же результат мог быть получен при использовании другого универсального музыкального файлового формата — WAV). Файлы в формате data:URI могут проигрываться всеми браузерами, за исключением Internet Explorer. Поэтому пользователи Internet Explorer просто не получают музыкального сопровождения для игры (хотя можно было бы только для IE подгружать его динамически, создавая тот же самый embed просто со ссылкой на внешний файл). 4.5. CSS Sprites и data:URI Давайте проведем сравнение двух освещенных выше методов для кардинального уменьшения числа запрашиваемых файлов с сервера: CSS Sprites и data:URI. Проблемы при верстке С какими проблемами сталкивается верстальщик, когда использует CSS Sprites? Это, в первую очередь, проблемы изменения каждой конкретной картинки в общем массиве. Для этого нужно открыть ресурсную картинку, найти в ней область, соответствующую данному небольшому изображению (которое меняется), и заменить ее, не потеряв палитру при всех изменениях. Также при изменении расположения картинок в ресурсном файле (например, перераспределили свободное место в связи с очередными дизайнерскими изменениями) нужно заново пересчитать все координаты и внести соответствующие изменения в CSS-файл. При небольшом количестве спрайтов или их равномерном распределении (например, иконок для пунктов меню) это будет не очень сложно. Но если ресурсный файл представляет собой набор картинок разных размеров, например как у Google? Тогда любое изменение может превратиться в ночной кошмар. В любом случае, на использование спрайтов тратится дополнительное время при разработке сайтов. Рис. 4.7. Пример CSS Sprites со страницы поиска Google. Источник: www.google.comТакже у IE возникают проблемы с позиционированием полупрозрачных PNG-картинок (которые нужно вставлять через AlpaImageLoader). Таким образом, больше одной такой картинки в спрайт не добавить (в левый верхний угол). Это можно обойти при помощи IE-фильтра crop или абсолютного позиционирования и дополнительной разметки (когда в контейнер с относительным позиционированием вставляем изображение с абсолютным позиционированием и накладываем поверх этого изображения все остальное содержимое контейнера — оно будет располагаться в нем в обычном порядке). Проблемы при загрузке Казалось бы, CSS Sprites призваны уменьшить задержку при загрузке страницы, однако на практике так происходит только при правильном подходе. Обычно в ресурсную картинку объединяется все подряд, картинка многократно увеличивается, а визуальная задержка при загрузке сайта только растет: пользователь может ждать 1 большую картинку дольше, чем половину входящих в нее маленьких, а последние обеспечат ему «почти» загрузку сайта. Если спрайтов у нас немного (1–2 картинки), то общее время загрузки сайта, скорее всего, возрастет. Это связано с тем, что браузер не сможет открыть как минимум 4-8 дополнительных соединений (к хостам, где расположена статика) и загрузить все исходные картинки параллельно, а не последовательно. При небольшой сетевой задержке это может оказать решающее воздействие. Проблемы при использовании Даже если положиться на то, что спрайты поддерживаются (почти) всеми браузерами на данный момент, все равно остается достаточно много вопросов, которые они не только не решают, а скорее сами создают. Во-первых, это проблемы при использовании иконок для списка. В таком случае необходимо располагать маленькие картинки «лесенкой», но в общем случае это увеличивает размер получившейся картинки на 20-30%. Во-вторых, при использовании спрайтов, когда пытаются обойти обрисованную проблему с «выползанием» фона для ненужных элементов, в структуре страницы появляются несемантические элементы (дополнительная разметка), которые, по сути, заменяют тег img, только через фоновое изображение. Такое засорение структуры страницы вредно отражается на всех уровнях разработки и использования сайта. В-третьих, если логотип склеен с другими картинками (как у вышеуказанного примера), то становится невозможным его использование как обычной картинки (например, в версии для печати или на внешних ресурсах). Шаг за шагом С развитием техник data:URI наиболее логичным выходом из сложившейся ситуации будет следующий характер разработки: Верстальщик создает рабочую версию макета сайта, не прибегая к помощи CSS Sprites (на каждый элемент — одна фоновая картинка). Веб-технолог внедряет кодирование картинок в base64 (+mhtml) в CSS-файл(ы) на этапе их загрузки на боевой сайт, создавая автоматизированное решение. На этом этапе могут использоваться и CSS Sprites, однако их внедрять сложнее из-за перерасчета позиционирования фона и обновления соответствующих стилевых правил. Веб-программист обеспечивает для ряда «старых» браузеров загрузку версии без использования data:URI. Чем это хорошо? Верстальщик не думает лишний раз, что и как ему расположить и нарезать: эти операции уже включены в процесс публикации сайта, автоматизированы и максимально адаптированы под пользователей. Чем это плохо? В общем случае, загрузка страницы не ускорится, а даже может замедлиться, потому что фоновые картинки (включенные через data:URI) будут грузиться в один поток, а не в несколько, как при обычном использовании спрайтов. Если фоновых картинок достаточно много (несколько десятков килобайтов), то это окажется существенным. При небольшом их объеме (до 10 Кб) будет заметно явное ускорение. Как распределить загрузку фоновых картинок между предзагрузкой и пост-загрузкой (фактически ускорив первую за счет замедления второй), рассказывается чуть ниже. Выносим CSS-файлы в пост-загрузку При использовании data:URI итоговый CSS-файл занимает довольно большой объем (фактически равный 110-120% от размера всех картинок и набору базовых CSS-правил). И это в виде архива. Если файл не заархивирован, то его дополнительный размер увеличивается многократно (в 2,5–3 раза относительно размера всех фоновых изображений), но это не так существенно, ибо пользователей с отключенным сжатием для CSS-файлов сейчас единицы (обычно доли процента).Для решения этой проблемы, во-первых, нам нужно разделить весь массив CSS-правил на относящиеся к фоновым изображениям и не относящиеся. Во-вторых, сообщить браузерам, что они могут отобразить страницу без первого массива правил (ведь если в нем содержатся только фоновые изображения, то они могут и подождать чуть-чуть).Фактически, используя такой подход, мы создаем другой контейнер для фоновых изображений (не ресурсное изображение, а CSS-файл), который удобнее использовать в большинстве случаев. Мы объединяем все фоновые картинки не через CSS Sprites, а через data:URI, и можем загрузить их все одним файлом (в котором каждая картинка будет храниться полностью независимо). При этом избегаем любых проблем с позиционированием фона (все ранее заявленные проблемы с (полу)прозрачными картинками для прошлых версий IE сохраняются, однако их решение также остается прежним).Теоретическое решение Все гениальное просто, поэтому мы можем загружать в самом начале страницы достаточно небольшой CSS-файл (без фоновых изображений, только базовые стили, чтобы только отобразить страницу корректно), потом по событию загрузки страницы (подробнее о методе можно прочитать в седьмой главе) через JavaScript подгрузить в один или несколько потоков динамические файлы стилей.Тут есть и возможные минусы: после загрузки каждого дополнительного CSS-файла будет происходить перерисовка страницы. Однако если таких файлов всего 1 или 2, то отображение страницы произойдет значительно быстрее.Почему мы не может распараллелить загрузку файлов стилей в самом начале документа? Потому что два файла будут загружаться медленнее, чем один (файлы загружаются последовательно в большинстве браузеров, поэтому задержки на установление соединений будут складываться). К тому же мы ратуем за максимально быстрое отображение страницы в браузере пользователя (завершение первой стадии загрузки), поэтому исходный объем загружаемого CSS должен быть минимальным (можно также рассмотреть варианты по включению его в сам HTML).На практике На практике все оказалось не сильно сложнее. Мы загружаем в head страницы (до вызовов любых внешних файлов) наш «легкий» CSS:<link href="light-light.css" rel="stylesheet" type="text/css" media="all"/> а затем добавляем в комбинированный обработчик window.onload (подробнее о нем рассказывается в седьмой главе) создание нового файла стилей, который дополняет уже загрузившуюся страницу фоновыми изображениями:function combinedWindowOnload() { load_dynamic_css("background-images.css"); ... } В результате мы имеем максимально быстрое отображение страницы, а затем стадию пост-загрузки, которая вытянет с сервера все дополнительные картинки (тут уже сам браузер постарается), стилевые правила и скрипты.А доступность? Внимательные читатели уже заготовили вопрос: а что, если у пользователя отключен JavaScript? Тут всё должно быть просто: мы добавляем соответствующий <noscript> для поддержки таких пользователей. С маленьким нюансом: <noscript> не может находиться в <head>, а <link> не может находиться в <body>. Если мы соблюдаем стандарты (все же иногда лучше довериться профессионалам и не ставить браузеры в неудобное положение, когда они встретятся с очередным отклонением от спецификации), то стоит искать обходные пути.После небольших экспериментов было выделено следующее изящное решение, обеспечивающее работу схемы во всех браузерах (замечание: после многочисленных экспериментов было решено остановиться на HTML-комментариях — они оказались наилучшим способом запретить загрузку указанного CSS-файла):<script type="text/javascript"> /* если мы сможем создать динамический файл стилей */ if (document.getElementsByTagName) { /* то добавляем в загрузку облегченную версию */ document.write('\x3clink href="light-light.css" rel="stylesheet" type="text/css" media="all"/>'); /* после этого начинаем HTML-комментарий */ document.write('\x3c--'); } </script> <link href="full.css" rel="stylesheet" type="text/css" media="all"/> <!--[if IE]><![endif]--> В результате браузер с включенным JavaScript запишет начало комментария, а закроет его только после <link> (комментарии не могут быть вложенными). При выключенном JavaScript <script> не отработает, <link> обработается и добавится в очередь загрузки, а последний комментарий будет просто комментарием.Делаем решение кроссбраузерным В ходе тестирования в Internet Explorer обнаружилось, что если добавлять файл стилей сразу параллельно со скриптами (в функции, которая для него срабатывает по onreadystatechange), то IE «морозит» первоначальную отрисовку страницы (т. е. показывает белый экран), пока не получит «свеженький» файл стилей. Для того чтобы Internet Explorer не занимался «замораживанием», нужно вставить фиктивную задержку следующим образом:setTimeout('load_dynamic_css("background-images.css")',0); В Safari же логика отображения страницы в зависимости от загружаемых файлов отличается от всех браузеров. Если в двух словах, то можно жестко определить начальный набор файлов, необходимых для отображения страницы на экране (HTML/CSS/JavaScript). А можно начать загружать все файлы в порядке приоритетности (и выполняя все их зависимости) и проверять время от времени, можно ли уже отобразить страницу (выполняя все вычисления в фоновом режиме без обновления экрана).У Safari второй подход, поэтому ничего лучше выноса загрузки динамического CSS-файла с фоновыми картинками после срабатывания window.onload для этого браузера пока не существует. Зато первоначальная картинка в браузере появляется значительно быстрее (при большом объеме фоновых изображений).Итак, давайте объявим функцию для создания динамического файла стилей:/* Объявляем функцию по динамической загрузке стилей и скриптов. */ function load_dynamic_css (src){ var node = document.createElement("link"); node = document.getElementsByTagName("head")[0].appendChild(node); node.setAttribute("rel", "stylesheet"); node.setAttribute("media", "all"); node.setAttribute("type", "text/css"); node.setAttribute("href", src); } ... /* Далее определяем для window обработчик по событию onload. Используем условную компиляцию для выделения IE */ window[/*@cc_on !@*/0 ? 'attachEvent' : 'addEventListener'] (/*@cc_on 'on' + @*/'load', function(){ setTimeout('load_dynamic_css("background-images.css")',0); } ,false); Выигрыш При наличии у вас большого количества маленьких декоративных фоновых изображений, которые к тому же могут повторяться по различным направлениям, может быть очень удобно объединить их все в один файл и загружать его после отображения страницы на экране.Описанная техника (кроссбраузерный data:URL плюс динамическая загрузка файлов стилей) позволяет добиться всех преимуществ технологии CSS Sprites, не затягивая загрузку страницы. При этом обладает очевидными преимуществами: не нужно лепить все картинки в один файл (их можно объединять на этапе публикации, а не на этапе разработки), можно работать с каждой совершенно отдельно, что позволяет добиться большей семантичности кода и большего удобства использования сайтов. К тому же это несколько сократит CSS-код за счет уничтожения необходимости применения background-position.Таким образом, data:URI (в смысле влияния на скорость загрузки) равносильны CSS Sprites (или даже предпочтительнее последней, если учесть, что для повторяющихся и полупрозрачных CSS Sprites придется создавать отдельные ресурсные файлы). В смысле же простоты внедрения и разработки они отличаются в выгодную сторону: нужно лишь настроить использование общей схемы один раз в шаблонах (с учетом динамической загрузки JavaScript-файлов, которая описана в седьмой главе, это все равно придется делать) и при публикации изменения применять base64-кодирование к фоновым изображениям.4.6. Методы экстремальной оптимизации Чем больше число внешних ресурсов, к которым браузер обращается при загрузке, тем больше время требуется для отображения страницы. Как правило, веб-страницы обращаются ко многим внешним CSS и файлам JavaScript. Все файлы стилей и скриптов можно объединить, чтобы уменьшить число внешних ресурсов этих типов до двух. Это, естественно, поможет серьезно сократить время загрузки страницы.Объединение JavaScript и CSS в одном файле Однако существует способ объединения CSS с JavaScript и сведения количества загрузок к одной. Техника основана на том, как CSS и анализатор JavaScript ведут себя в IE и Firefox.Когда анализатор CSS сталкивается с символом комментария HTML (<!--) в содержании CSS, символ игнорируется. Когда анализатор JavaScript сталкивается с символом комментария HTML (<!--) в содержании JavaScript, символ рассматривают как подобный комментарию линии (//), и, следовательно, остальная часть строки после символа комментария HTML игнорируется. Рассмотрим на примере <!-- /* function t(){} <!-- */ <!-- body { background-color: white; } Когда анализатор CSS будет разбирать вышеупомянутый код, символы комментария HTML будут пропущены, и код станет эквивалентным следующему примеру:/* function t(){} */ body { background-color: white; } Анализатор CSS видит только CSS-код, а код скрипта закомментирован (/* ... */).Когда анализатор JavaScript станет разбирать код, символы комментария HTML будут интерпретированы в комментарии строки (//), и, следовательно, код станет таким:// /* function t(){} // */ // body { background-color: white; } Анализатор JavaScript видит только код скрипта, а все остальное закомментировано. Чтобы ссылаться на этот ресурс, можно использовать теги <script> и <link> на странице. Например:<link type="text/css" rel="stylesheet" href="test.jscss" /> <script type="text/javascript" src="test.jscss"></script> Заметим, что эти два тега ссылаются на один тот же ресурс и, следовательно, он загрузится всего один раз и будет интерпретирован и как стили, и как скрипты.Есть еще одна вещь, о которой стоит позаботиться, — Content-Type ответа. Его необходимо выставлять в */*, чтобы дать подтверждение Firefox: содержание может быть обработано как что-либо подходящее (как стили или как скрипты).Указанное решение не работает в Safari (1-5% пользователей), однако конкретно для этого браузера (определив его через User-Agent) уже можно вставить загрузку еще одного файла.Объединение HTML, CSS и JavaScript в одном файле Чтобы избежать дополнительных запросов со стороны браузера, можно включить непосредственно стилей и(ли) скриптов в сам HTML-документ.Здесь стоит остановиться на следующем моменте: если размер CSS- (или JavaScript-) файла больше, чем 20% (и при этом больше 5 Кб в сжатом виде), лучше вынести его как отдельный компонент. Это позволит настроить его кэширование для постоянных пользователей вашего сайта.Рассматривать включение всех ресурсов в исходную HTML-страницу стоит только в том случае, если достаточно большой процент посетителей (больше 90%) пришли на нее в первый и (возможно) в последний раз. Тогда эта технология будет замечательно работать: кэширование ничего практически не даст, а дополнительные запросы к серверу замедлят загрузку страницы для новых, незнакомых со спецификой сайта посетителей (что может быть решающим фактором для их окончательного ухода).Во всех остальных случаях — когда можно выделить достаточно большие ресурсные файлы или когда достаточное количество пользователей приходят не в первый раз — такой подход неприменим.Как рабочий пример можно привести заглавные страницы Яндекса и Google — на них вызывается минимум внешних ресурсов, а стилевые правила включены в саму страницу.Внутри или снаружи? Давайте в качестве заключения рассмотрим следующий вопрос: стоит ли вообще подключать JavaScript- и CSS-файлы или можно включить весь их код непосредственно в код страницы? Использование подключаемых файлов на практике обычно дает более быстрые страницы, т. к. браузеры кэшируют файлы скриптов и CSS. JavaScript- и CSS-код, который находится в HTML, загружается каждый раз при загрузке самого HTML-документа. Это уменьшает количество необходимых HTTP-запросов, но увеличивает объем HTML. С другой стороны, если скрипты и таблицы стилей находятся в отдельных файлах, закэшированных браузером, размер HTML уменьшается, не увеличивая при этом количество HTTP-запросов (при повторных посещениях). В таком случае ключевым фактором является частота, с которой кэшируются внешние JavaScript- и CSS-файлы относительно количества запросов самого HTML-документа. И хотя этот фактор очень сложно посчитать, его можно приблизительно оценить различными способами. Если пользователи во время одного посещения загружают страницу несколько раз или загружают похожие страницы, которые используют один и тот же код, — это именно тот случай, когда мы можем получить все преимущества от вынесения кода в отдельные файлы. Многие сайты только наполовину удовлетворяют этим условиям. Для таких случаев в целом лучшим решением будет создание внешних файлов скриптов и таблиц стилей. Единственное исключение, которое можно здесь привести (когда прямое добавление кода дает большое преимущество) — это использование его на главных страницах, таких как главная страница Яндекса ( http://www.yandex.ru/ ), Рамблера ( http://www.rambler.ru/ ) или Google ( http://www.google.ru/ ). Для страниц, которые загружаются всего несколько (обычно — один) раз за весь сеанс, выгодней включать скрипты и таблицы стилей прямо в HTML-документ, чтобы выиграть в скорости загрузки. Для таких главных страниц, которые открываются первыми в ряду других с этого же сайта, существует возможность уменьшить число HTTP-запросов еще и следующим образом: мы можем включить JavaScript и CSS в код самой страницы, однако после ее полной загрузки динамически подгружать внешние файлы стилей и скриптов для последующего использования (на стадии пост-загрузки). При этом следующие страницы будут использовать уже закэшированные файлы. |
|
||
Главная | Контакты | Нашёл ошибку | Прислать материал | Добавить в избранное |
||||
|