javascript - Suprimiendo 404s en la biblioteca retina.js
http-status-code-404 (7)
Utilizamos el js lib retina.js que intercambia imágenes de baja calidad con imágenes "retina" (tamaño por 2). El problema es que retina.js lanza un 404 para cada imagen de "retina" que no se puede encontrar.
Somos propietarios de un sitio donde los usuarios pueden cargar sus propias imágenes que probablemente no estén en una resolución de retina.
¿No hay manera de evitar que el js lance 404s?
Si no conoces la lib. Aquí está el código que arroja el 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();
Hay algunas opciones que veo, para mitigar esto.
Mejorar y persistir el almacenamiento en caché de resultados de llamadas HTTP de retina.js
Para cualquier imagen ''2x'' determinada que esté configurada para intercambiar una versión ''1x'', retina.js primero verifica la disponibilidad de la imagen a través de una solicitud XMLHttpRequest
. Las rutas con respuestas exitosas se almacenan en caché en una matriz y la imagen se descarga.
Los siguientes cambios pueden mejorar la eficiencia:
Los intentos fallidos de verificación de
XMLHttpRequest
se pueden almacenar en la memoria caché: actualmente, un intento de verificación de ruta ''2x'' se omite solo si ha tenido éxito anteriormente. Por lo tanto, los intentos fallidos pueden repetirse. En la práctica, esto no importa mucho porque el proceso de verificación ocurre cuando la página se carga inicialmente. Pero, si los resultados persisten, el seguimiento de las fallas evitará errores 404 recurrentes.localStorage
los resultados de la verificación de la ruta ''2x'' enlocalStorage
: durante la inicialización, retina.js puede verificar quelocalStorage
un caché de resultados. Si se encuentra uno, el proceso de verificación para las imágenes ''2x'' que ya se han encontrado se puede omitir y la imagen ''2x'' se puede descargar o omitir. Las rutas de imagen de "2x" recién encontradas se pueden verificar y los resultados se pueden agregar al caché. Teóricamente, mientras quelocalStorage
está disponible, se generará un 404 solo una vez para una imagen por navegador. Esto se aplicaría en todas las páginas de cualquier página del dominio.
Aquí hay un rápido tratamiento. La funcionalidad de caducidad probablemente debería ser agregada.
https://gist.github.com/4343101/revisions
Emplear un encabezado de redireccionamiento HTTP
Debo tener en cuenta que mi comprensión de los asuntos del "lado del servidor" es irregular , en el mejor de los casos. Por favor, tome este FWIW
Otra opción es que el servidor responda con un código de redireccionamiento para solicitudes de imágenes que tienen los caracteres @2x
y no existen. Ver esta respuesta relacionada .
En particular:
Si redirecciona las imágenes y se pueden almacenar en la memoria caché, lo ideal sería establecer un encabezado HTTP Expires (y el encabezado apropiado de Cache-Control) para una fecha en el futuro lejano, por lo que al menos en las visitas posteriores a la página los usuarios no tendrán Pasar por la redirección de nuevo.
El empleo de la respuesta de redireccionamiento eliminaría los 404 y provocaría que el navegador omita los intentos posteriores de acceder a las rutas de imágenes ''2x'' que no existen.
retina.js puede hacerse más selectivo
Los retinajs se pueden modificar para excluir algunas imágenes.
Una solicitud de extracción relacionada con esto: https://github.com/imulus/retinajs/commit/e7930be
Según la solicitud de extracción, en lugar de encontrar elementos <img>
por nombre de etiqueta, se puede usar un selector CSS y esta puede ser una de las opciones configurables de retina.js. Se puede crear un selector de CSS que filtrará las imágenes subidas por el usuario (y otras imágenes para las cuales se espera que no exista una variante ''2x'').
Otra posibilidad es agregar una función de filtro a las opciones configurables. La función se puede llamar en cada elemento <img>
coincidente; un return true
causaría la descarga de una variante ''2x'' y cualquier otra cosa causaría que se <img>
.
La configuración básica y predeterminada cambiaría de la versión actual a algo como:
var config = {
check_mime_type: true,
retinaImgTagSelector: ''img'',
retinaImgFilterFunc: undefined
};
La función Retina.init()
cambiaría de la versión actual a algo como:
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();
}
};
Para ponerlo en práctica, antes de que se window.onload
, llama a:
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;
}
});
Actualización: Limpiado y reorganizado. Se agregó la mejora localStorage
.
Mi sugerencia es que reconozca que los errores 404 son errores verdaderos y que los corrija de la forma en que se supone que deben hacerlo, que es proporcionar gráficos de Retina. Hizo que sus scripts fueran compatibles con Retina, pero no completó el círculo al hacer que su flujo de trabajo de gráficos fuera compatible con Retina. Por lo tanto, los gráficos Retina en realidad faltan. Independientemente de lo que ocurra al inicio de su flujo de trabajo de gráficos, la salida del flujo de trabajo debe ser de 2 archivos de imagen, una de baja resolución y Retina 2x.
Si un usuario carga una foto que es 3000x2400, debe considerar que es la versión Retina de la foto, marcarla 2x y luego usar un script del lado del servidor para generar una versión más pequeña no Retina 1500x1200, sin la 2x. Los 2 archivos juntos constituyen una imagen compatible con Retina 1500x1200 que se puede mostrar en un contexto web a 1500x1200, ya sea que la pantalla sea Retina o no. No tiene que preocuparse porque tiene una imagen compatible con Retina y un sitio web compatible con Retina. El script RetinaJS es el único que debe preocuparse de si un cliente está utilizando Retina o no. Por lo tanto, si está recopilando fotos de usuarios, su tarea no estará completa a menos que genere 2 archivos, tanto de baja resolución como de alta resolución.
El teléfono inteligente típico captura una foto que es más de 10 veces el tamaño de la pantalla del teléfono inteligente. Así que siempre deberías tener suficientes píxeles. Pero si está obteniendo imágenes realmente pequeñas, como 500px, entonces puede establecer un punto de interrupción en su script de reducción de imágenes del lado del servidor, de modo que debajo de eso, la foto cargada se usa para la versión de baja resolución y el script hace una copia 2x eso no va a ser mejor que la imagen no Retina, pero será compatible con Retina.
Con esta solución, todo el problema de "¿está la imagen 2x o no?" Desaparece, porque siempre está ahí. El sitio web compatible con Retina simplemente utilizará su base de datos de fotos compatible con Retina sin ninguna queja.
Prefiero un poco más de control sobre qué imágenes se reemplazan.
Para todas las imágenes para las que he creado una @ 2x, cambié el nombre de la imagen original para incluir @ 1x. (* Vea la nota a continuación.) Cambié retina.js ligeramente, de modo que solo vea [nombre] @ 1x. [Ext] imágenes.
Reemplacé la siguiente línea en retina-1.1.0.js:
retinaImages.push(new RetinaImage(image));
Con las siguientes líneas:
if(image.src.match(/@1x/./w{3}$/)) {
image.src = image.src.replace(/@1x(/./w{3})$/,"$1");
retinaImages.push(new RetinaImage(image));
}
Esto hace que retina.js solo reemplace @ 1x imágenes con nombre con @ 2x imágenes con nombre.
(* Nota: al explorar esto, parece que Safari y Chrome reemplazan automáticamente las imágenes @ 1x con imágenes @ 2x, incluso sin retina.js instalado. Soy demasiado vago para rastrear esto, pero me imagino que es una característica con Los últimos navegadores webkit. Retina.js y los cambios anteriores son necesarios para la compatibilidad con varios navegadores.
Retina JS admite el atributo data-no-retina en la etiqueta de imagen. De esta forma no intentará encontrar la imagen de la retina.
Útil para otras personas que buscan una solución simple.
<img src="/path/to/image" data-no-retina />
Una de las soluciones es usar PHP:
Reemplace el código de la primera publicación con:
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();
y en la raíz del sitio, agregue el archivo llamado "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 es una buena herramienta para imágenes fijas en páginas web estáticas, pero si está recuperando imágenes cargadas por el usuario, la herramienta correcta es el lado del servidor. Me imagino PHP aquí, pero la misma lógica puede aplicarse a cualquier lenguaje del lado del servidor.
Siempre que un buen hábito de seguridad para las imágenes cargadas es no permitir que los usuarios las alcancen por url directo: si el usuario carga un script malicioso en su servidor, no podrá iniciarlo a través de una url ( www.yoursite.com/uploaded/mymaliciousscript.php
). Por lo general, es un buen hábito recuperar las imágenes cargadas a través de algún script <img src="get_image.php?id=123456" />
si puede ... (y aún mejor, mantenga la carpeta de carga fuera de la raíz del documento)
Ahora el script get_image.php puede obtener la imagen apropiada 123456.jpg o [email protected] dependiendo de algunas condiciones.
El enfoque de http://retina-images.complexcompulsions.com/#setupserver parece perfecto para su situación.
Primero establece una cookie en su encabezado cargando un archivo a través de JS o CSS:
Dentro de la cabeza:
<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>
Al comienzo del CUERPO:
<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>
Ahora, cada vez que se llame a su secuencia de comandos para recuperar imágenes cargadas, tendrá un conjunto de cookies que solicitará imágenes de retina (o no).
Por supuesto, puede usar el script retinaimages.php provisto para generar las imágenes, pero también puede modificarlo para satisfacer sus necesidades dependiendo de cómo produzca y recupere imágenes de una base de datos u oculte el directorio de carga de los usuarios.
Por lo tanto, no solo puede cargar la imagen adecuada, sino que si GD2 está instalado y mantiene la imagen cargada original en el servidor, incluso puede cambiar su tamaño y recortar en consecuencia y guardar los 2 tamaños de imagen almacenados en caché en el servidor. Dentro de las fuentes retinaimages.php puedes ver (y copiar) cómo funciona:
<?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..
?>
Ventajas: la imagen se carga solo una vez (los usuarios de retina en 3G al menos no cargan imágenes 1x + 2x), funciona incluso sin JS si las cookies están habilitadas, se pueden activar y desactivar fácilmente, sin necesidad de usar las convenciones de nomenclatura de Apple. Cargas la imagen 12345 y obtienes el DPI correcto para tu dispositivo.
Con la reescritura de url, puede incluso hacerla totalmente transparente redirigiendo /get_image/1234.jpg a /get_image.php?id=1234.jpg
Respuesta corta: no es posible usar solo JavaScript del lado del cliente
Después de examinar el código y un poco de investigación, me parece que retina.js no está realmente lanzando los errores 404.
Lo que retina.js realmente está haciendo es solicitar un archivo y simplemente realizar una comprobación de si existe o no en función del código de error. Lo que realmente significa que está pidiendo al navegador que verifique si el archivo existe. El navegador es lo que te da el 404 y no hay una forma de navegador cruzado para evitarlo (digo "navegador cruzado" porque solo marqué el kit web).
Sin embargo, lo que podría hacer si esto realmente es un problema es hacer algo en el lado del servidor para evitar los 404s por completo.
Esencialmente, esto sería, por ejemplo, /retina.php?image= YOUR_URLENCODED_IMAGE_PATH una solicitud a la que podría devolver esto cuando exista una imagen de retina ...
{"isRetina": true, "path": "YOUR_RETINA_IMAGE_PATH"}}
y esto si no ...
{"isRetina": false, "path": "YOUR_REGULAR_IMAGE_PATH"}}
Luego, puede hacer que JavaScript llame a este script y analizar la respuesta según sea necesario. No estoy diciendo que sea la única o la mejor solución, solo una que funcione.