Статьи

Автор: Мациевский Николай aka sunnybear
Опубликована: 11 июня 2008

Кроссбраузерное использование data:URL

После статей картинки в теле страницы с помощью data:URL и data URL в IE мне написал один из читателей и предложил метод использования base64-кодирования в CSS-файлах под IE (как это сделать в HTML-файлах, описано в последней статье). После этого прошло пара месяцев, прежде, чем мне довелось взяться за рассмотрение этого метода более детально. Однако, после нескольких недель исследований удалось получить весьма обнадеживающую картину.

О чем идет речь? IE (до версии 7 включительно) не поддерживает протокол data:URL, а вместе с ним base64-кодирование внешних файлов и включение их прямо в тело необходимого документа (будь то HTML или CSS/JS-файл). Однако, если рассмотреть использование протокола mhtml (который, конечно же, поддерживается только в IE), многое становится более ясным, и base64-кодирование удается использовать в полной мере.

От простого к сложному

Здесь и далее я буду приводить примеры из файла webo.in/a.css.gz, в котором успешно воплощены описанные ниже наработки.

Классическое применение data:URL заключается во включении небольших фоновых картинок прямо в соответствующий CSS-файл примерно следующим образом:

div.q {
	background:#fff url(data:image/png;base64,iVBOR...) repeat-x;
}

Как я уже писал, у такого подхода 2 сложности: картинки кешируются вместе с CSS-файлом (т.е. если нам нужно внести изменения в CSS, то пользователю придется, фактически, загрузить все картинки заново) и данный метод не поддерживается IE (что гораздо хуже, естественно).

Однако, некоторый выход из сложившегося положения все же есть.

О, этот странный Microsoft!

В IE существует альтернативное решение для вставки изображений прямо в текстовый документ в виде mhtml-включений. Более подробно об этом написано в соответствующей статье, я лишь остановлюсь на практической реализации для CSS-файлов (большое спасибо Константину за предоставленный пример mhtml в CSS-файле).

/*
Content-Type: multipart/related; boundary="_"

--_
Content-Location:1
Content-Transfer-Encoding:base64

iVBOR..
*/

Далее в CSS-файле нужно лишь вызвать эту картинку следующим образом:

div.q {
	background-image:url(mhtml:http://webo.in/a.css.gz?20080531!1);
}

Здесь в адресе картинки идет протокол mhtml: (который поддерживается исключительно в IE, но это не так важно), далее полный URL до CSS-файла (который содержит эту картинку), В моем случае этот URL еще и содержит GET-параметр для соответствующего кеширования. Это небольшая тонкость использования данного формата. Необходимо в URL использовать ту же строку, что и в HTML-файле, в котором подключается данный CSS, иначе IE запросит CSS-файл дважды: первый раз как таблицу стилей, второй раз — как хранилище картинки. Далее после восклицательного знака (!) идет тот идентификатор, который мы назначили картинке в Content-Location. И все.

Объединяем несовместимое

Итак. С одной стороны у нас схема data:URL, которая поддержана W3C и распознается всеми браузерами, кроме IE. С другой стороны у нас IE, который понимает mhtml и которым пользуются 70% наших пользователей. Чувствуете направление мысли? Правильно, мы объединим эти два решения, благо, они оба используют base64-представление картинок.

Задача первая: объединить оба назначения стилевых правил, чтобы они не конфликтовали друг с другом. Решается это не просто, а очень просто с помощью либо отдельного CSS-файла для IE (через условные комментарии), либо CSS-хаков (последнее в моем случае предпочтительнее, ибо позволяет загружать на клиент всего 1 CSS-файл). В итоге, в CSS-файле мы имеем примерно следующее:

/*
Content-Type: multipart/related; boundary="_"

--_
Content-Location:1
Content-Transfer-Encoding:base64

iVBOR..
*/

div.q {
	background:#fff url(data:image/png;base64,iVBOR...) repeat-x;
}
* html div.q {
	background-image:url(mhtml:http://webo.in/a.css.gz?20080531!1);
}
*+html div.q {
	background-image:url(mhtml:http://webo.in/a.css.gz?20080531!1);
}

Данная конструкция позволяет вывести фоновое изображение в base64-кодировке для всех (ну или 99,9%) браузеров. Почему в конце содержатся 2 разных CSS-селектора с одним объявлением? Первое предназначено для IE6-, второе — для IE7. Объединить через запятую их нельзя (по крайней мере, у меня не получилось).

Панацея или ящик Пандоры?

Первый же скептический вопрос, который каждый уважающий себя оптимизатор задаст: как можно эти картинки выводить только один раз в CSS-файле (внимательный читатель уже заметил, что base64-строка появляется там дважды)? Спрашивали-отвечаем. Никак. Однако, выход есть.

Это gzip-сжатие. Если очень коротко, то при любом архивировании создается некоторый словарь наиболее часто используемых строк, которые заменяются на однобайтовые представления. При восстановлении данных из архива происходит обратная процедура, когда значения слов словаря подставляются вместо их наименований. В нашем случае у нас для одной картинки используется одна и та же base64-строка. Это означает, что при архивировании она сожмется удивительно эффективно.

Замечу также, что при таком подходе результирующий сжатый файл занимает даже меньше, чем при назначении с помощью CSS-хаков для IE внешних изображений для загрузки, что проверено на практике.

Валидность

Получившийся таким образом CSS-файл абсолютно валиден (так как все mhtml-вставки происходят в комментариях). Каждое CSS-объявление валидно с точки зрения CSS 2.1 (не утвержденной, кстати, еще спецификации), те небольшие приемы, которые позволяют эффективно сжать данные для всех браузеров, не отражаются на восприятии ими файла. Стили показываются — так что еще нужно для полноты счастья?

Хочу сразу оговориться, что такой подход считаю уместным только для CSS. Для HTML ситуация совсем другая (о ней немного подробнее ниже). HTML-документ при таком подходе оказывается невалидным, что выливается во множественные проблемы верстки, и это не та же цена, которую можно заплатить за некоторую не очень понятную производительность.

А HTML?

С HTML-документами ситуация одновременно и проще, и сложнее. Я опишу то решение, которое считаю уместным на основе своих предпочтений и текущей практики создания новых сайтов. Оно не является идеальным, просто отражает мое текущее представление о создании оптимальных веб-приложений.

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

Суть его заключается в том, чтобы для нормальных браузеров вставить изображение в base64, а только для IE вставить его ссылкой на внешний файл. Как это реализуется:

<div class="v">
	<img src="data:image/png;base64,iVBOR..." alt="Base64"/>
	<!--[if lte IE7]><img src="chart.png" alt="external"/><![endif]-->
</div>

Вся загвоздка теперь заключается в том, что у нас нет хаков, позволяющих не показывать часть HTML-кода только для IE, есть условные комментарии, способные не показать часть HTML-кода только для IE (<!--[if !IE]>-->I'm not IE<!--<![endif]-->, спасибо посмотреть профиль Lynn, их использование оставлю в качестве самостоятельно задания для читателей), и все же, это нам не помешает воплотить нашу затею в жизнь. Для этого мы используем связку position:relativeposition:absolute и соответствующие поля.

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

div.v {
	position:relative;
}

А для вложенных изображений:

div.v img {
	position:absolute;
}

Таким образом, нормальные браузеры не прочитают условные комментарии и покажут изображение в base64, а IE их прочитает и поверх непонятной картинки наложит вполне понятную, запрошенную из внешнего файла. Результирующий CSS-код будет выглядеть примерно так:

div.v {
	position:relative;
	margin-top:высота;
}
div.v img {
	position:absolute;
	width: ширина;
	height: высота;	
}

Мы позаботились о том, чтобы абсолютное позиционирование картинки никак не отразилось на взаимном расположении блоков. Для этого мы выделили в контейнере с изображениями достаточно свободного места (через margin), которое визуально будет занято нашим изображением. Результат можно увидеть на главной странице webo.in.

Известные неприятности

Как было выяснено экспериментальным путем, mhtml не поддерживает (полу)прозрачные картинки. Поэтому их придется все вставлять отдельными файлами (если вы таковые используете) есть проблемы с отображением mhtml в IE7 под Vista (это малоизвестный баг). На данный момент решений через data:URI / mhtml не известно.

Также хочу повторить, что использование mhtml делает HTML-файлы невалидными по спецификации W3C, будьте с этим аккуратны.

Подводим итоги

  • Картинки в CSS можно вставлять с помощью data:URL.
  • Для IE можно использовать mhtml, полностью дублирующий эту функциональность.
  • Для корректного применения стилей нужны CSS-хаки со звездочкой (star hacks).
  • Используем gzip для CSS-файлов для устранения последствий множественного использования base64-строки.
  • data:URL и mhtml можно вставлять и в HTML, однако, mhtml делает его совсем не валидным.
  • Можно использовать CSS-позиционирование и условные комментарии для подключения внешней картинки только для IE вместо data:URL. Валидность при этом сохраняется.

Что я хочу показать данной статьей? То, что использовать base64-кодирование на ваших страницах можно уже прямо сегодня. Очень важно при этом понимать возможные последствия и, по возможности, обойтись малой кровью. Однако, с выходом IE8 и массовым его проникновением ситуация кардинально изменится (ведь он поддерживает data:URL), поэтому лучше быть готовым к этим последствиям прямо сейчас.

Повторюсь: я не призывая вас повторять проделанные эксперименты, однако, они смогут найти практическое приложение в вашей деятельности или стать некоторым заделом на будущее, к которому вы еще вернетесь, когда наступит подходящий момент. Как говорится, «осведомлен — значит, вооружен».

Читать дальше

Все комментарии (habrahabr.ru)