Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Кроссбраузерный data URI в CSS-файлах (многабукв)

Прелюдия

Некоторое время назад, я нашёл способ, использования data URI в Internet Explorer. Data URI — специальный вид URL, который включает в себя код самого объекта. Например, таким образом можно внедрять изображения прямо в код HTML-страницы.

Выглядит такой HTML вот так:

<img src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP…и так далее" />

Где «data» — это протокол URI, image/gif — тип объекта (картинка GIF), base64 — тип кодирования, после запятой идёт сам код картинки.

Такой вид URI понимают сейчас все современные браузеры, включая Internet Explorer 8 (правда, со многими ограничениями). Плохо то, что Internet Explorer 7 и ниже этого не поддерживает, а доля этих версий настолько велика (около 50% на данный момент по данным LiveInternet), что до недавнего времени data URI использовалась крайне редко.

Я, увлёкшись исследованием малодокументированного протокола mhtml (MIME HTML), поддерживаемого в браузерах Microsoft с пятой версии, в одном из экспериментов разобрался как внедрить объект в тело HTML-документа и, что важнее, придумал как совместить оба способа (data URI и MHTML) в одном документе.

Вообще mhtml был придумал для адресации внутри так называемых «веб-архивах» — формат, очень похожий на формат писем электронной почты и предназначенный для сохранения веб-страниц вместе с картинками, стилями, JavaScript и прочим в одном файле. Сейчас он, так или иначе, поддерживается большинством браузеров.

Типичный mhtml-адрес выглядит следующим образом:

mhtml:http://example.net/index.html!fhhryregdy4gere

Где после «mhtml» идёт обычный веб-адрес, а после восклицательного знака — идентификатор объекта внутри веб-архива. Как я уже сказал, веб-архив очень похож на почтовое сообщение, где один из методов кодирования объектов — base64, тот же самый, который применяется и в dataURI.

В итоге, когда мне пришла эта идея, я совместил оба способа так, чтобы Internet Explorer видел файл как веб-архив с закодированным base64 объектом, а остальные браузеры видели этот закодированный объект как часть dataURI.

Специфика HTML допускает переносы, в том числе и внутри data URI, поэтому мне удалось задуманное относительно быстро. В CSS всё сложнее, а тема использования data URI в CSS, как оказалось, актуальнее, чем для HTML. Например, в книге Николая Мациевского «Разгони свой сайт» сделана попытка расширения моего способа на CSS, к сожалению, менее удачная, чем хотелось бы: требуется включение закодированного объекта два раза.

Фуга

Итак, мне захотелось посмотреть, не получится ли расширить мой способ на CSS, не прибегая к включению закодированного файла два раза. Причём, сфера тут уже, подключаемый к CSS объект это, в 99,99% — картинка.

После немногочисленных экспериментов, оказалось, что оторвать закодированный объект от начала data URI не представляется возможным, что мешает использованить mhtml — там заголовок и кодированный объект должны отделяться пустой строкой.

Тем не менее, других способов побороть IE я не видел и потому продолжал эксперименты. Несколько слов о том, что такое base64. Base64, по сути, анахронизм, доставшийся нам от электронной почты на заре её зарождения. Письма тогда ходили исключительно в латинице и все почтовые системы полагали, что никаких других символов в письмах не бывает.

Таким образом, когда встала задача передавать в письмах что-то кроме англоязычных текстов (например, файлы или тексты на других языках), было придумано несколько способов кодирования таких объектов. Один из них — base64 (для математиков — это позиционная система счисления с основанием 64). 64 — это количество знаков, которая использует это кодирование. Это символы A-Z, a-z, 0-9 и плюс со слешем.

Хорошие новости, заключаются в том, что браузеры игнорируют в кодированной строке символы, которые не входят в упомянутые 64. Можно было ожидать, что браузеры вообще откажутся разбирать такую строку.

Итак, строка data URI начинается с чего-то вроде «data:image/gif;base64,» и в CSS не терпит переносов, а MHTML требует, чтобы кодированная строка начиналась с начала строки.

Из вышесказанного следует, что нужно как-то сделать начало data URI частью кодированной строки. Тут надо немного рассказать из чего состоят принятые в вебе стандарты кодирования изображений.

Оставим в покое XBM, BMP, ART, WMF/EMF, JPEG200 и прочую экзотику, поддерживаемую браузерами, и остановимся на триплете JPEG, GIF, PNG. Честно сказать с форматом GIF я знаком плохо, а JPEG и PNG исследовал несколько лет назад довольно подробно.

JPEG и PNG состоят из секций. В общих чертах, сначала идёт заголовок файла, потом сами секции, в формате (с некоторыми вариациями) «имя секции», «длина секции», «конец секции». И у JPEG, и у PNG есть секции для произвольного содержимого. У JPEG это COM и APPn, у PNG — iTXt, tEXt и zTXt. Кстати, и у GIF тоже есть похожее место — Application Extension.

Отсюда идея: Internet Explorer, декодировав файл с начала строки, получает заголовок с секцией из содержимого, куда помещается заголовок для остальных браузеров и мусор, оставшийся от декодирования начала data URI, остальные браузеры должны пропустить заголовок для IE и начать декодирование с data URI.

Получаем, на примере JPEG:

FF D8 — заголовок JPEG для IE FF E0 xx xx — секция APP0, куда прячется всё до данных изображения, «;background-color:url(data:image/jpeg;base64,» — это видят остальные браузеры FF D8 — начало JPEG для остальных браузеров «данные изображения» — это место видят уже все браузеры

Все бинарные данные выравниваются и кодируются (строка начала CSS-стиля для остальных браузеров не кодируется и IE декодирует её как мусор внутри APP0).

В моём примере такой CSS выглядит вот так:

/9j/4AAs;background-image:url(data:image/jpeg;base64;BOLKNOTE.RU,/9j/4AAC…

Легко увидеть, как видит этот стиль браузер: «/9j/4AAs» кажется ему ошибочным атрибутом (его, кстати, можно взять в комментарий), а «;» — разделителем атрибутов. И хорошо просматривается («/9j/AA») двойной старт заголовка JPEG-файла. IE начнёт декодирование с первого заголовка, другие браузеры — со второго.

Остальное должно быть понятно из примера («!ie» — CSS hack, делающий стиль видимым только для IE), как это выглядит в браузере, можно увидеть в разделе «Храню» у меня на сайте.

24 комментария
reon.livejournal.com 2009

Классно придумано, но в IE6 под Windows2000 не работает...

Евгений Степанищев (bolknote.ru) 2009

Комментарий для reon.livejournal.com:

А вот это работает? http://bolknote.ru/files/ie-data-url-2.php

Есть какая-то разновидность IE с глюком обработки MHTML. Есть свидетельства, что это какая-то очень редкая разновидность IE6SP1.

0range (0range.ru) 2009

Комментарий для Евгения Степанищева:

Эт помоему самый первый релиз ие6, там багов туча, через 3 месяца все это пофиксили, но почему то до сих пор встречается и откуда ток берут :/

Евгений Степанищев (bolknote.ru) 2009

Комментарий для 0range.ru:

Не знаю… Восемь лет не обновлять браузер! В чём причина, не понимаю.

0range (0range.ru) 2009

Комментарий для Евгения Степанищева:

Да просто чуваки 8 лет назад купили установочный диск, с виндой 2000 и до сих пор у них он лежит, переставляют систему наверн время от времени и все их устраивает
А по сути это мелкософтовский косяк, посмотри как автообновление в мозилле реализовано, и как в ие

Евгений Степанищев (bolknote.ru) 2009

Комментарий для 0range.ru:

Mozilla — отдельный браузер, тогда как IE (до 8-й версии) неотъемлемая часть системы, потому и обновляется только через систему. В этом ничего плохого нет, Apple на Винде тоже обновляется свои продукты кучей (и Google — тоже).

Мне хочется добраться как-нибудь до этого браузера и посмотреть что же с ним не так.

0range (0range.ru) 2009

Комментарий для Евгения Степанищева:

Ну приоритетность пусть делают мега высокую! :) чтоб всевремя всплывало автообновление, рано или поздно юзер обновится и все, А на маках народ умный, обычно все апдейты ставят

Евгений Степанищев (bolknote.ru) 2009

Комментарий для 0range.ru:

На маках народ разных. Наверное половина «Яндекса» на Маках. Кто-то ставит, кто-то нет. Было бы лучше, если бы новые версии IE входили в SP. Вот это было бы круто.

Michael Yakovis (yakovis.com) 2009

Комментарий для 0range.ru:

Имею подозрение, что одним движением обновить ie, скажем, до восьмерки, ms мешает не нежелание обновить, а нежелание судиться по очередному антимонопольному делу.

0range (0range.ru) 2009

Комментарий для Евгения Степанищева:

Эхх поиду поднимать еще одну систему у ся, чтоб ие8 ставить :/
Кстати в gmail’e галочку проставь что хистори у тя сохранялось, помоему из-за того что этой галочки нету я те не могу сообщения отправить када ты оффлайн.

0range (0range.ru) 2009

Комментарий для yakovis.com:

Мда вот поэтому я не люблю крупные корпорации.. Все там сложно :(

Евгений Степанищев (bolknote.ru) 2009

Комментарий для yakovis.com:

А причём тут антимонопольное? Не догоняю…

Michael Yakovis (yakovis.com) 2009

Комментарий для Евгения Степанищева:

Да я тоже не очень, но подозреваю. Иначе почему бы не?

Евгений Степанищев (bolknote.ru) 2009

Комментарий для yakovis.com:

Пользователи очень неохотно привыкают к изменениям интерфейса браузера, может поэтому?

blog.ad.by 2010

На всякий случай, вдруг кто не видел.
http://www.phpied.com/data-uris-mhtml-ie7-win7-vista-blues/

greli.livejournal.com 2010

Ошибочка: «;background-color:url(data:image/jpeg;base64,» работать не будет, надо просто background (без «-color»).
Как отмечено в последнем комментарии по предыдущей ссылке, проблема с кэшированием в IE7 решена.

Евгений Степанищев (bolknote.ru) 2010

Комментарий для greli.livejournal.com:

Да, опечатался, спасибо!

Евген 2010

У меня на IE 6 SP 2 нижеприведённая ссылка работает с data:uri в CSS, а в теле они не работают. Так что может и нет смысла в CSS c mhtml мудрить? ;)
http://uggallery.audiopeace.ru/data-uri-red-filler/

Евгений Степанищев (bolknote.ru) 2010

Комментарий для Евген:

Internet Explorer начал поддерживать data URI с версии 8 и то не полностью. IE6 не поддерживает data URI, вы ошибаетесь. Либо вы используете какой-нибудь IETester и он не работает правильно, либо обновили браузер и не заметили этого.

Евген 2010

Я использую не тестер, а IE 6.0.2900.2180.xpsp_sp2_qfe.070719-1309
и IE 6.0.2900.2180.xpsp_sp2_rtm.040803-2158 на виртуальной машине
Когда я в них открываю вышеприведённую ссылку — я вижу большой красноватый полупрозрачный квадрат поверх текста (это сработал дата ури из CSS) и 2 незагруженных изображения внизу страницы (это не сработали дата ури в теле). Я пишу это не ради того, чтобы поспорить, просто констатирую факт. Может это глюк. Может это из-за !important в CSS. А может я просто туплю. ;)

Евген 2010

Да, это я туплю, не работает. ;)
Картинку заменил, в ФФ работает, в ИЕ не работает.

Alex 2012

Давняя статья, но актуальности не утратила.
Не уверен, что получу ответ, но всё же ...

Идея совместить в одном base64-коде возможности MHTML + data:URL (MIMO + CSS) — гениальная!

Одна проблема — как верстальщику, который не «исследовал несколько лет довольно подробно» форматы изображений найти тот пресловутый «заголовок» файла в base64-коде.
Мои эксперименты с GIF и PNG увы, ничего не дали.
Результат нулевой — всё работает только если перед css-свойством вписан весь base64-код. :-)

[цитата]
И у JPEG, и у PNG есть секции для произвольного содержимого. У JPEG это COM и APPn, у PNG — iTXt, tEXt и zTXt. Кстати, и у GIF тоже есть похожее место — Application Extension.
[/цитата]

  1. Каким образом этим можно воспользоваться при поиске «секции для произвольного содержимого» в файле с кодом base64 ?
  1. Как определить тот минимальный «заголовок» в коде base64 для JPG, PNG8, GIF ?
    Имеется ли маска или алгоритм поиска в тексте base64?
  1. Имеются ли ссылки, где детально описывается вивисекция текста base64-кода на секции и разделы у GIF и т. д.?

Спасибо!

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Alex:

На «Хабре» есть прекрасная статья по мотивам моей идеи: http://habrahabr.ru/post/90761/
С подробным разбором как что можно сделать, даже со скриптами.

Михаил 2018

Интересное решение! По моему такое есть в SLAED CMS.
Поправьте если ошибаюсь, их сайт: https://slaed.net