Подавление 404s в библиотеке retina.js
Мы используем js lib retina.js, который заменяет изображения низкого качества изображениями "сетчатки" (размер 2). Проблема в том, что retina.js выдает 404 за каждое изображение "сетчатки", которое не может быть найдено.
У нас есть сайт, где пользователи могут загружать свои собственные изображения, которые, скорее всего, не в разрешении сетчатки.
Нет ли способа помешать js бросить 404s?
Если вы не знаете библиотеку. Вот код, выбрасывающий 404:
http = new XMLHttpRequest;
http.open('HEAD', this.at_2x_path);
http.onreadystatechange = function() {
if (http.readyState != 4) {
return callback(false);
}
if (http.status >= 200 && http.status <= 399) {
if (config.check_mime_type) {
var type = http.getResponseHeader('Content-Type');
if (type == null || !type.match(/^image/i)) {
return callback(false);
}
}
RetinaImagePath.confirmed_paths.push(that.at_2x_path);
return callback(true);
} else {
return callback(false);
}
}
http.send();
7 ответов
Есть несколько вариантов, которые я вижу, чтобы смягчить это.
Улучшите и сохраните кэширование результатов HTTP-вызовов retina.js
Для любого данного изображения "2x", для которого установлена замена версии "1x", retina.js сначала проверяет доступность изображения через XMLHttpRequest
запрос. Пути с успешными ответами кэшируются в массиве, и изображение загружается.
Следующие изменения могут повысить эффективность:
Не удалось
XMLHttpRequest
попытки проверки могут быть кэшированы: в настоящее время попытка проверки пути "2x" пропускается, только если она ранее была успешной. Поэтому неудачные попытки могут повториться. На практике это не имеет большого значения, потому что процесс проверки происходит при начальной загрузке страницы. Но, если результаты сохранятся, отслеживание сбоев предотвратит повторяющиеся ошибки 404.Сохранение результатов проверки пути "2x" приводит к
localStorage
: Во время инициализации retina.js может проверитьlocalStorage
для кеша результатов. Если один из них найден, процесс проверки для изображений "2x", который уже встречался, можно обойти, и изображение "2x" можно либо загрузить, либо пропустить. Недавно обнаруженные пути к изображениям "2x" можно проверить, а результаты добавить в кэш. Теоретически покаlocalStorage
доступно, 404 будет происходить только один раз для изображения для каждого браузера. Это относится ко всем страницам для любой страницы в домене.
Вот быстрое обследование. Функциональность истечения, вероятно, должна быть добавлена.
https://gist.github.com/4343101/revisions
Использовать заголовок перенаправления HTTP
Я должен отметить, что мое понимание "серверных" вопросов в лучшем случае нечеткое. Пожалуйста, возьмите этот FWIW
Другой вариант для сервера - ответить кодом перенаправления на запросы изображений, которые имеют @2x
персонажи и не существуют. Смотрите этот связанный ответ.
Особенно:
Если вы перенаправляете изображения и они кешируются, в идеале вы должны установить заголовок HTTP Expires (и соответствующий заголовок Cache-Control) для даты в далеком будущем, чтобы, по крайней мере, при последующих посещениях страницы пользователи не имели снова пройти через редирект.
Использование ответа перенаправления избавило бы от 404-х и заставило бы браузер пропускать последующие попытки доступа к несуществующим путям изображений "2x".
retina.js можно сделать более избирательным
Retinajs могут быть изменены, чтобы исключить некоторые изображения из рассмотрения.
Запрос на получение, связанный с этим: https://github.com/imulus/retinajs/commit/e7930be
По запросу, вместо поиска <img>
элементы по имени тега, можно использовать селектор CSS, и это может быть одним из настраиваемых параметров retina.js. Можно создать селектор CSS, который будет отфильтровывать загруженные пользователем изображения (и другие изображения, для которых вариант "2x" не существует).
Другая возможность - добавить функцию фильтра к настраиваемым параметрам. Функция может быть вызвана на каждом совпавшем <img>
элемент; return true
приведет к загрузке варианта "2x", а все остальное вызовет <img>
быть пропущенным.
Базовая конфигурация по умолчанию изменится с текущей версии на что-то вроде:
var config = {
check_mime_type: true,
retinaImgTagSelector: 'img',
retinaImgFilterFunc: undefined
};
Retina.init()
функция изменится с текущей версии на что-то вроде:
Retina.init = function(context) {
if (context == null) context = root;
var existing_onload = context.onload || new Function;
context.onload = function() {
// uses new query selector
var images = document.querySelectorAll(config.retinaImgTagSelector),
retinaImages = [], i, image, filter;
// if there is a filter, check each image
if (typeof config.retinaImgFilterFunc === 'function') {
filter = config.retinaImgFilterFunc;
for (i = 0; i < images.length; i++) {
image = images[i];
if (filter(image)) {
retinaImages.push(new RetinaImage(image));
}
}
} else {
for (i = 0; i < images.length; i++) {
image = images[i];
retinaImages.push(new RetinaImage(image));
}
}
existing_onload();
}
};
Чтобы применить это на практике, прежде чем window.onload
пожары, звоните:
window.Retina.configure({
// use a class 'no-retina' to prevent retinajs
// from checking for a retina version
retinaImgTagSelector : 'img:not(.no-retina)',
// or, assuming there is a data-owner attribute
// which indicates the user that uploaded the image:
// retinaImgTagSelector : 'img:not([data-owner])',
// or set a filter function that will exclude images that have
// the current user's id in their path, (assuming there is a
// variable userId in the global scope)
retinaImgFilterFunc: function(img) {
return img.src.indexOf(window.userId) < 0;
}
});
Обновление: очищено и реорганизовано. Добавил localStorage
повышение.
Краткий ответ: невозможно использовать только клиентский JavaScript
После просмотра кода и небольшого исследования мне кажется, что retina.js на самом деле не выдает 404 ошибки.
На самом деле retina.js запрашивает файл и просто проверяет, существует ли он на основе кода ошибки. Что на самом деле означает, что он просит браузер проверить, существует ли файл. Браузер - это то, что дает вам 404, и нет кроссбраузерного способа предотвратить это (я говорю "кроссбраузерный", потому что я проверял только webkit).
Однако, что вы могли бы сделать, если это действительно проблема, - это сделать что-то на стороне сервера, чтобы вообще предотвратить 404.
По сути, это будет, например, /retina.php?image=YOUR_URLENCODED_IMAGE_PATH запрос, который может вернуть его, когда изображение сетчатки существует...
{"isRetina": true, "path": "YOUR_RETINA_IMAGE_PATH"}}
и это, если это не так...
{"isRetina": false, "path": "YOUR_REGULAR_IMAGE_PATH"}}
Затем вы могли бы заставить некоторый JavaScript вызывать этот скрипт и анализировать ответ по мере необходимости. Я не утверждаю, что это единственное или лучшее решение, просто оно подойдет.
Retina JS поддерживает атрибут data-no-retina для тега изображения. Таким образом, он не будет пытаться найти изображение сетчатки.
Полезно для других людей, ищущих простое решение.
<img src="/path/to/image" data-no-retina />
Я предпочитаю немного больше контролировать, какие изображения будут заменены.
Для всех изображений, для которых я создал @2x, я изменил исходное имя изображения, добавив @1x. (* См. Примечание ниже.) Я немного изменил retina.js, чтобы он смотрел только на изображения [name]@1x.[Ext].
Я заменил следующую строку в retina-1.1.0.js:
retinaImages.push(new RetinaImage(image));
Со следующими строками:
if(image.src.match(/@1x\.\w{3}$/)) {
image.src = image.src.replace(/@1x(\.\w{3})$/,"$1");
retinaImages.push(new RetinaImage(image));
}
Это делает так, что retina.js заменяет только @ 1x именованные изображения на @2x именованные изображения.
(* Примечание: при изучении этого, кажется, что Safari и Chrome автоматически заменяют изображения @ 1x изображениями @2x, даже без установленного retina.js. Мне лень это отследить, но я предполагаю, что это особенность с последние версии веб-браузеров. Как таковые, retina.js и вышеуказанные изменения необходимы для поддержки кросс-браузер.)
Одним из решений является использование PHP:
заменить код из 1-го поста на:
http = new XMLHttpRequest;
http.open('HEAD', "/image.php?p="+this.at_2x_path);
http.onreadystatechange = function() {
if (http.readyState != 4) {
return callback(false);
}
if (http.status >= 200 && http.status <= 399) {
if (config.check_mime_type) {
var type = http.getResponseHeader('Content-Type');
if (type == null || !type.match(/^image/i)) {
return callback(false);
}
}
RetinaImagePath.confirmed_paths.push(that.at_2x_path);
return callback(true);
} else {
return callback(false);
}
}
http.send();
и в корень вашего сайта добавьте файл с именем "image.php":
<?php
if(file_exists($_GET['p'])){
$ext = explode('.', $_GET['p']);
$ext = end($ext);
if($ext=="jpg") $ext="jpeg";
header("Content-Type: image/".$ext);
echo file_get_contents($_GET['p']);
}
?>
retina.js - хороший инструмент для фиксированных изображений на статических веб-страницах, но если вы извлекаете загруженные пользователем изображения, правильный инструмент - на стороне сервера. Я представляю себе PHP здесь, но та же логика может быть применена к любому языку на стороне сервера.
При условии, что хорошей привычкой безопасности для загружаемых изображений является запрет на доступ пользователей к ним по прямой ссылке: если пользователю удастся загрузить вредоносный скрипт на ваш сервер, он не сможет запустить его через URL ( www.yoursite.com/uploaded/mymaliciousscript.php
). Поэтому обычно полезно загружать загруженные изображения с помощью какого-либо сценария. <img src="get_image.php?id=123456" />
если вы можете... (и даже лучше, держите папку загрузки вне корня документа)
Теперь скрипт get_image.php может получить соответствующее изображение 123456.jpg или 123456@2x.jpg в зависимости от некоторых условий.
Подход http://retina-images.complexcompulsions.com/ кажется идеальным для вашей ситуации.
Сначала вы устанавливаете cookie в своем заголовке, загружая файл через JS или CSS:
Внутри головы:
<script>(function(w){var dpr=((w.devicePixelRatio===undefined)?1:w.devicePixelRatio);if(!!w.navigator.standalone){var r=new XMLHttpRequest();r.open('GET','/retinaimages.php?devicePixelRatio='+dpr,false);r.send()}else{document.cookie='devicePixelRatio='+dpr+'; path=/'}})(window)</script>
В начале тела:
<noscript><style id="devicePixelRatio" media="only screen and (-moz-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2)">#devicePixelRatio{background-image:url("/retinaimages.php?devicePixelRatio=2")}</style></noscript>
Теперь каждый раз, когда вызывается ваш скрипт для извлечения загруженных изображений, у него будет набор файлов cookie, запрашивающий изображения сетчатки (или нет).
Конечно, вы можете использовать предоставленный скрипт retinaimages.php для вывода изображений, но вы также можете изменить его в соответствии с вашими потребностями в зависимости от того, как вы создаете и извлекаете изображения из базы данных, или скрываете каталог загрузки от пользователей.
Таким образом, не только он может загружать соответствующий образ, но если установлен GD2 и вы сохраняете исходный загруженный образ на сервере, он может даже изменить его размер и соответственно обрезать и сохранить 2 размера кэшированных изображений на сервере. Внутри источников retinaimages.php вы можете увидеть (и скопировать), как это работает:
<?php
$source_file = ...
$retina_file = ....
if (isset($_COOKIE['devicePixelRatio'])) {
$cookie_value = intval($_COOKIE['devicePixelRatio']);
}
if ($cookie_value !== false && $cookie_value > 1) {
// Check if retina image exists
if (file_exists($retina_file)) {
$source_file = $retina_file;
}
}
....
header('Content-Length: '.filesize($source_file), true);
readfile($source_file); // or read from db, or create right size.. etc..
?>
Плюсы: изображение загружается только один раз (пользователи Retina на 3G, по крайней мере, не будут загружать изображения 1x+2x), работает даже без JS, если включены cookie-файлы, может легко включаться и выключаться, нет необходимости использовать соглашения об именах Apple. Вы загружаете изображение 12345, и вы получаете правильный DPI для вашего устройства.
С переписыванием URL вы можете даже сделать его полностью прозрачным, перенаправив /get_image/1234.jpg в /get_image.php?id=1234.jpg
Я предлагаю вам признать ошибки 404 истинными и исправить их так, как вы предполагаете, то есть предоставить графику Retina. Вы сделали свои сценарии Retina-совместимыми, но не завершили круг, сделав графический процесс Retina-совместимым. Поэтому графика Retina фактически отсутствует. Что бы ни появлялось в начале вашего графического рабочего процесса, выход рабочего процесса должен состоять из 2 файлов изображений, с низким разрешением и Retina 2x.
Если пользователь загружает фотографию размером 3000x2400, вы должны рассмотреть ее как версию фотографии Retina, отметьте ее 2x, а затем используйте серверный сценарий для создания меньшей версии 1500x1200 без Retina, без 2x. Два файла вместе составляют одно изображение, совместимое с Retina, размером 1500x1200, которое может отображаться в веб-контексте с разрешением 1500x1200 независимо от того, отображается ли изображение на Retina. Вам не нужно беспокоиться, потому что у вас есть Retina-совместимое изображение и Retina-совместимый веб-сайт. Сценарий RetinaJS - единственный, который должен заботиться о том, использует ли клиент Retina или нет. Поэтому, если вы собираете фотографии у пользователей, ваша задача не будет выполнена, если вы не сгенерируете 2 файла, как с низким, так и с высоким разрешением.
Типичный смартфон делает снимок, размер которого в 10 раз превышает размер дисплея смартфона. Поэтому у вас всегда должно быть достаточно пикселей. Но если вы получаете действительно маленькие изображения, например, 500 пикселей, то вы можете установить точку останова в своем сценарии уменьшения изображений на стороне сервера, чтобы ниже этого загруженная фотография использовалась для версии с низким разрешением, а сценарий делал 2-кратную копию. это будет не лучше, чем изображение без Retina, но будет совместимым с Retina.
С этим решением вся ваша проблема "есть ли 2х изображение или нет?" Исчезнет, потому что она всегда есть. Retina-совместимый веб-сайт просто и без проблем будет использовать вашу Retina-совместимую базу данных фотографий.