Идея данной статьи заключается в следующем: можно уменьшить количество запросов HTTP, которые страница должна сделать для своих изображений, предварительно обработав исходный код и преобразовав их по схеме data:URI.
Схема data:URI позволяет включать в веб-страницу данные в качестве внешнего ресурса. Схема может быть использована для любого вида данных, включая изображения, сценарии и таблицы стилей, и поддерживается во всех современных браузерах: Gecko браузерах, таких как Firefox и Camino, Webkit браузерах, как Safari, Konqueror и Chrome, конечно же, в Opera, и с ограничениями в IE8 (но не в IE7 или более ранней версии).
Как показывает Google, я не первый, кому пришла идея использовать схему для оптимизации страниц. Но все реализации, с которыми я встречался, вращались вокруг переписывания путей к изображениям вручную, чтобы указать на них в сценарии, как в следующем примере.
1 2 |
<?php <img alt="Darwin Fish" src="%3C?php%20echo%20data_uri%28%27images/darwinfish.png%27%29;%20?%3E"/> |
Что я предлагаю, так это ретроспективный процесс, который преобразует все пути к изображениям автоматически, поэтому вам не придется делать ничего, кроме разработки страницы.
Код, лежащий в основе
Страница содержит пять элементов < img> и одно CSS фоновое изображение, но во всех поддерживаемых браузерах страница вообще не посылает дополнительных HTTP запросов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
<?php if($datauri_supported = preg_match("/(Opera|Gecko|MSIE 8)/", $_SERVER['HTTP_USER_AGENT'])) { ob_start(); } ?> <style type="text/css"> <br> body <br> { <br> background:url(images/texture.jpeg) #e2e2dc repeat; <br> color:#554; <br> } <br> </style> <p> <img alt="dropcap.jpg" src="images/dropcap.jpg"/> <img alt="firefox.png" src="images/firefox.png"/> <img alt="specificity.jpg" src="images/specificity.jpg"/> <img alt="darwinfish.png" src="images/darwinfish.png"/> <img alt="rolleyes.gif" src="images/rolleyes.gif"/> </p> if($datauri_supported) { function create_data_uri($matches) { $filetype = explode('.', $matches[2]); $filetype = strtolower($filetype[count($filetype) - 1]); if(!preg_match('/^(gif|png|jp[e]?g|bmp)$/i', $filetype)) { return $matches[0]; } if(preg_match('/^//', $matches[2])) { $matches[2] = $_SERVER['DOCUMENT_ROOT'] . $matches[2]; } @$data = base64_encode(file_get_contents($matches[2])); return $matches[1] . "data:image/$filetype;base64,$data" . $matches[3]; } $html = ob_get_contents(); ob_end_clean(); $html = preg_split("/ ? | /", $html); while(count($html) > 0) { $html[0] = preg_replace_callback("/(src=[\"'])([^\"']+)([\"'])/", 'create_data_uri', $html[0]); $html[0] = preg_replace_callback("/(url(['\"]?)([^\"')]+)([\"']?))/", 'create_data_uri', $html[0]); echo $html[0] " "; array_shift($html); } } ?> |
Как всё это работает
Основой кода является возможность создавать data URI с использованием base64-кодированных изображений.
Но кроме этого, необходимо применить несколько ключевых приемов, чтобы всё это заработало. Во-первых, это использование буфера вывода для предварительной компиляции исходного вывода, чтобы была возможность проанализировать его еще раз, прежде чем отправить в браузер. Если у вас не имеется свежая версия Mozila Firefox, то советую загрузить Мазилу сейчас.
Вы увидите, что в самом начале кода я установил условный оператор, который проверяет тип браузера и начинает буферизацию вывода. Это же условие используется снова, окружая основной код внизу сценария, поэтому для браузеров, которые не поддерживают эту технику, весь сценарий пропускается, и вывод страницы происходит в обычном режиме. Затем я разделил HTML с помощью разрыва строки, чтобы можно было выполнить обработку, вывести, а затем сразу удалить каждую строку, что позволяет избежать необходимости держать весь исходный код в памяти.
Во-вторых, для осуществления разбора страницы я использовал функцию обратного вызова preg_replace_callback, которая определяет HTML и CSS пути с помощью пары регулярных выражений, и выполняет обработку, для которой обычной замены было бы недостаточно. (Необходимо искать атрибуты SRC и URL свойства отдельно, так как их синтаксис слишком сильно отличается и одного регулярного выражения будет недостаточно)
В функции обратного вызова сначала нужно выяснить тип файла, который необходим для вывода данных и для определения разрешенных типов, поэтому можно отвергать все, что это не является изображением (например, SCRIPT SRC). Массив $matches, который передается в функцию, всегда содержит всю подстроку, соответствующую шаблону регулярного выражения в качестве первого элемента, поэтому если тип файла нам не подходит, можно просто вернуть в качестве результата этот первый элемент без изменений и всё.
Осталось проверить путь к корневому каталогу, перед которым нужно записать DOCUMENT_ROOT, чтобы создать правильный путь к файлу. Теперь можно кодировать изображение (с подавлением ошибок, в случае если исходный путь был нарушен), затем собрать всё вместе и вернуть data URI. Всё просто!
Когда оптимизация уже не является оптимизацией?
Когда затраты больше экономии! Есть несколько потенциальных затрат, которые мы должны рассмотреть.
Изображения в data:URI на треть больше оригинала. Такие изображения не кэшируются, по крайней мере, как изображения, но они кэшируются как часть исходного кода. Такое кэширование является слишком избыточным, но, по крайней мере, позволяет просмотр в автономном режиме.
Также первое место в потреблении вычислительной мощности занимает преобразование изображения, и к тому же файл становится больше. При загрузке не происходит значительной задержки, поскольку файл находится на том же сервере, но base64-кодировка является сравнительно дорогостоящим процессом.
Поэтому оптимальным способом применения данной методики было бы её использование не для всех изображений, а только для большого количества маленьких изображений, таких как иконки или фоновые рисунки. Дополнительный размер кода будет незначительным, а время обработки небольшим, при этом создается заметное преимущество от сокращения нескольких десятков запросов, и страница загружается быстро.
Например, если вам нужно обрабатывать только GIF изображения, это легко сделать, изменив разрешенные типы изображений в регулярном выражении.
1 2 3 4 5 6 |
<?php if(!preg_match('/^(gif)$/i', $filetype)) { return $matches[0]; } ?> |
Также можно использовать функцию filesize, чтобы фильтровать изображения по размеру и обрабатывать только те, которые меньше определенного порога.
1 2 3 4 5 6 |
<?php if(filesize($matches[2]) > 1024) { return $matches[0]; } ?> |
Некоторые браузеры устанавливают ограничения на размер data: URI, но на практике я их не встречал. Firefox, Opera, Safari и даже IE8 без проблем выводили данные изображений более 1 Мб.