He visto muchas variaciones en las respuestas sobre cómo hacer esto, así que pensé en resumirlas aquí (además de agregar un cuarto método de mi propia invención):
(1) Agregue un parámetro de consulta único que revienta la caché a la URL, como:
newImage.src = "image.jpg?t=" + new Date().getTime();
Pros: 100% confiable, rápido y fácil de entender e implementar.
Contras: omite el almacenamiento en caché por completo, lo que significa retrasos innecesarios y el uso de ancho de banda siempre que la imagen no cambie entre las vistas. ¡Potencialmente llenará el caché del navegador (y cualquier caché intermedio) con muchas copias de exactamente la misma imagen! Además, requiere modificar la URL de la imagen.
Cuándo usar: se usa cuando la imagen cambia constantemente, como en el caso de una transmisión de cámara web en vivo. Si utiliza este método, asegúrese de servir las imágenes con Cache-control: no-cache
encabezados HTTP. (A menudo, esto se puede configurar usando un archivo .htaccess). ¡De lo contrario, irás llenando cachés progresivamente con versiones antiguas de la imagen!
(2) Agregue el parámetro de consulta a la URL que cambia solo cuando el archivo lo hace, por ejemplo:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(Ese es el código del lado del servidor PHP, pero el punto importante aquí es solo que una cadena de consulta ? M = [tiempo de última modificación del archivo] se agrega al nombre del archivo).
Pros: 100% confiable, rápido y fácil de entender e implementar, y conserva perfectamente las ventajas de almacenamiento en caché.
Contras: Requiere modificar la URL de la imagen. Además, un poco más de trabajo para el servidor: tiene que obtener acceso a la hora de última modificación del archivo. Además, requiere información del lado del servidor, por lo que no es adecuado para una solución exclusivamente del lado del cliente para buscar una imagen actualizada.
Cuándo usarlo: cuando desea almacenar en caché las imágenes, pero es posible que deba actualizarlas al final del servidor de vez en cuando sin cambiar el nombre del archivo. Y cuando puede asegurarse fácilmente de que se agrega la cadena de consulta correcta a cada instancia de imagen en su HTML.
(3) Sirva sus imágenes con el encabezado Cache-control: max-age=0, must-revalidate
y agregue un identificador único de fragmento que revienta la memoria caché a la URL, como:
newImage.src = "image.jpg#" + new Date().getTime();
La idea aquí es que el encabezado de control de caché coloca las imágenes en la memoria caché del navegador, pero inmediatamente las marca como obsoletas, de modo que y cada vez que se vuelven a mostrar, el navegador debe verificar con el servidor para ver si han cambiado. Esto garantiza que la memoria caché HTTP del navegador siempre devuelva la última copia de la imagen. Sin embargo, los navegadores a menudo reutilizarán una copia en memoria de una imagen si tienen una, y ni siquiera verificarán su caché HTTP en ese caso. Para evitar esto, se utiliza un identificador de fragmento: la comparación de las imágenes en memoria src
incluye el identificador de fragmento, pero se elimina antes de consultar el caché HTTP. (Entonces, por ejemplo, image.jpg#A
y image.jpg#B
ambos pueden mostrarse desde la image.jpg
entrada en el caché HTTP del navegador, peroimage.jpg#B
nunca se mostraría utilizando datos de imagen retenidos en la memoria de la image.jpg#A
última vez que se mostró).
Pros: hace un uso adecuado de los mecanismos de almacenamiento en caché HTTP y utiliza imágenes almacenadas en caché si no han cambiado. Funciona para servidores que se ahogan en una cadena de consulta agregada a una URL de imagen estática (ya que los servidores nunca ven identificadores de fragmentos, son para uso exclusivo de los navegadores).
Contras: se basa en el comportamiento algo dudoso (o al menos mal documentado) de los navegadores, con respecto a las imágenes con identificadores de fragmentos en sus URL (sin embargo, lo he probado con éxito en FF27, Chrome33 e IE11). Todavía envía una solicitud de revalidación al servidor para cada vista de imagen, lo que puede ser excesivo si las imágenes solo cambian raramente y / o la latencia es un gran problema (ya que debe esperar la respuesta de revalidación incluso cuando la imagen en caché aún es buena) . Requiere modificar las URL de las imágenes.
Cuándo usarlo: use cuándo las imágenes pueden cambiar con frecuencia, o si el cliente necesita actualizarlas de manera intermitente sin la participación del script del lado del servidor, pero donde aún desea la ventaja del almacenamiento en caché. Por ejemplo, sondear una cámara web en vivo que actualiza una imagen de forma irregular cada pocos minutos. Alternativamente, use en lugar de (1) o (2) si su servidor no permite cadenas de consulta en URL de imágenes estáticas.
(4) Actualice por la fuerza una imagen en particular usando Javascript, primero cargándola en un oculto <iframe>
y luego llamando location.reload(true)
al iframe contentWindow
.
Los pasos son:
Cargue la imagen para actualizarla en un iframe oculto. Este es solo un paso de configuración: si lo desea, puede realizar la actualización real con mucha anticipación. ¡Ni siquiera importa si la imagen no se carga en esta etapa!
Una vez hecho esto, deje en blanco todas las copias de esa imagen en su (s) página (s) o en cualquier lugar en cualquier nodo DOM (incluso los que están fuera de la página almacenados en variables de JavaScript). Esto es necesario porque el navegador puede mostrar la imagen de una copia en memoria obsoleta (IE11 hace esto especialmente): debe asegurarse de que se borran todas las copias en memoria antes de actualizar la caché HTTP. Si otro código de JavaScript se ejecuta de forma asincrónica, es posible que también deba evitar que ese código cree nuevas copias de la imagen que debe actualizarse mientras tanto.
Llamada iframe.contentWindow.location.reload(true)
. Las true
fuerzas de un bypass caché, recarga directamente desde el servidor y sobrescribir la copia en caché existente.
Una vez que haya terminado de volver a cargar, restaure las imágenes en blanco. ¡Ahora deberían mostrar la versión nueva del servidor!
Para imágenes del mismo dominio, puede cargar la imagen en el iframe directamente. Para las imágenes entre dominios, debe cargar una página HTML de su dominio que contenga la imagen en una <img>
etiqueta; de lo contrario, recibirá un error de "Acceso denegado" al intentar llamar iframe.contentWindow.reload(...)
.
Pros: ¡ Funciona igual que la función image.reload () que desearía que tuviera el DOM! Permite que las imágenes se almacenen en caché normalmente (incluso con fechas de vencimiento futuras si así lo desea, evitando así la revalidación frecuente). Le permite actualizar una imagen en particular sin alterar las URL de esa imagen en la página actual o en cualquier otra página, utilizando solo el código del lado del cliente.
Contras: se basa en Javascript. No se garantiza al 100% que funcione correctamente en todos los navegadores (aunque lo he probado con éxito en FF27, Chrome33 e IE11). Muy complicado en relación con los otros métodos.
Cuándo usarlo: cuando tiene una colección de imágenes básicamente estáticas que le gustaría almacenar en caché, pero aún necesita poder actualizarlas ocasionalmente y obtener comentarios visuales inmediatos de que la actualización se realizó. (Especialmente cuando la actualización de toda la página del navegador no funcionaría, como en algunas aplicaciones web creadas en AJAX, por ejemplo). Y cuando los métodos (1) - (3) no son factibles porque (por cualquier razón) no puede cambiar todas las URL que podrían mostrar la imagen que necesita actualizar. (Tenga en cuenta que al usar esos 3 métodos, la imagen se actualizará, pero si otra página intenta mostrar esa imagen sin la cadena de consulta adecuada o el identificador de fragmento, puede mostrar una versión anterior).
Los detalles de implementar esto de una manera robusta y flexible se dan a continuación:
Supongamos que su sitio web contiene un .gif en blanco de 1x1 píxeles en la ruta URL /img/1x1blank.gif
, y también tiene el siguiente script PHP de una línea (solo se requiere para aplicar la actualización forzada a las imágenes entre dominios , y se puede reescribir en cualquier lenguaje de script del lado del servidor , por supuesto) en la ruta URL /echoimg.php
:
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
Entonces, aquí hay una implementación realista de cómo podría hacer todo esto en Javascript. Parece un poco complicado, pero hay muchos comentarios, y la función importante es forceImgReload (): las dos primeras imágenes en blanco y en blanco, y deben estar diseñadas para funcionar de manera eficiente con su propio HTML, así que codifíquelas como funciona mejor para ti; Muchas de las complicaciones en ellos pueden ser innecesarias para su sitio web
// This function should blank all images that have a matching src, by changing their src property to /img/1x1blank.gif.
// ##### You should code the actual contents of this function according to your page design, and what images there are on them!!! #####
// Optionally it may return an array (or other collection or data structure) of those images affected.
// This can be used by imgReloadRestore() to restore them later, if that's an efficient way of doing it (otherwise, you don't need to return anything).
// NOTE that the src argument here is just passed on from forceImgReload(), and MAY be a relative URI;
// However, be aware that if you're reading the src property of an <img> DOM object, you'll always get back a fully-qualified URI,
// even if the src attribute was a relative one in the original HTML. So watch out if trying to compare the two!
// NOTE that if your page design makes it more efficient to obtain (say) an image id or list of ids (of identical images) *first*, and only then get the image src,
// you can pass this id or list data to forceImgReload() along with (or instead of) a src argument: just add an extra or replacement parameter for this information to
// this function, to imgReloadRestore(), to forceImgReload(), and to the anonymous function returned by forceImgReload() (and make it overwrite the earlier parameter variable from forceImgReload() if truthy), as appropriate.
function imgReloadBlank(src)
{
// ##### Everything here is provisional on the way the pages are designed, and what images they contain; what follows is for example purposes only!
// ##### For really simple pages containing just a single image that's always the one being refreshed, this function could be as simple as just the one line:
// ##### document.getElementById("myImage").src = "/img/1x1blank.gif";
var blankList = [],
fullSrc = /* Fully qualified (absolute) src - i.e. prepend protocol, server/domain, and path if not present in src */,
imgs, img, i;
for each (/* window accessible from this one, i.e. this window, and child frames/iframes, the parent window, anything opened via window.open(), and anything recursively reachable from there */)
{
// get list of matching images:
imgs = theWindow.document.body.getElementsByTagName("img");
for (i = imgs.length; i--;) if ((img = imgs[i]).src===fullSrc) // could instead use body.querySelectorAll(), to check both tag name and src attribute, which would probably be more efficient, where supported
{
img.src = "/img/1x1blank.gif"; // blank them
blankList.push(img); // optionally, save list of blanked images to make restoring easy later on
}
}
for each (/* img DOM node held only by javascript, for example in any image-caching script */) if (img.src===fullSrc)
{
img.src = "/img/1x1blank.gif"; // do the same as for on-page images!
blankList.push(img);
}
// ##### If necessary, do something here that tells all accessible windows not to create any *new* images with src===fullSrc, until further notice,
// ##### (or perhaps to create them initially blank instead and add them to blankList).
// ##### For example, you might have (say) a global object window.top.blankedSrces as a propery of your topmost window, initially set = {}. Then you could do:
// #####
// ##### var bs = window.top.blankedSrces;
// ##### if (bs.hasOwnProperty(src)) bs[src]++; else bs[src] = 1;
// #####
// ##### And before creating a new image using javascript, you'd first ensure that (blankedSrces.hasOwnProperty(src)) was false...
// ##### Note that incrementing a counter here rather than just setting a flag allows for the possibility that multiple forced-reloads of the same image are underway at once, or are overlapping.
return blankList; // optional - only if using blankList for restoring back the blanked images! This just gets passed in to imgReloadRestore(), it isn't used otherwise.
}
// This function restores all blanked images, that were blanked out by imgReloadBlank(src) for the matching src argument.
// ##### You should code the actual contents of this function according to your page design, and what images there are on them, as well as how/if images are dimensioned, etc!!! #####
function imgReloadRestore(src,blankList,imgDim,loadError);
{
// ##### Everything here is provisional on the way the pages are designed, and what images they contain; what follows is for example purposes only!
// ##### For really simple pages containing just a single image that's always the one being refreshed, this function could be as simple as just the one line:
// ##### document.getElementById("myImage").src = src;
// ##### if in imgReloadBlank() you did something to tell all accessible windows not to create any *new* images with src===fullSrc until further notice, retract that setting now!
// ##### For example, if you used the global object window.top.blankedSrces as described there, then you could do:
// #####
// ##### var bs = window.top.blankedSrces;
// ##### if (bs.hasOwnProperty(src)&&--bs[src]) return; else delete bs[src]; // return here means don't restore until ALL forced reloads complete.
var i, img, width = imgDim&&imgDim[0], height = imgDim&&imgDim[1];
if (width) width += "px";
if (height) height += "px";
if (loadError) {/* If you want, do something about an image that couldn't load, e.g: src = "/img/brokenImg.jpg"; or alert("Couldn't refresh image from server!"); */}
// If you saved & returned blankList in imgReloadBlank(), you can just use this to restore:
for (i = blankList.length; i--;)
{
(img = blankList[i]).src = src;
if (width) img.style.width = width;
if (height) img.style.height = height;
}
}
// Force an image to be reloaded from the server, bypassing/refreshing the cache.
// due to limitations of the browser API, this actually requires TWO load attempts - an initial load into a hidden iframe, and then a call to iframe.contentWindow.location.reload(true);
// If image is from a different domain (i.e. cross-domain restrictions are in effect, you must set isCrossDomain = true, or the script will crash!
// imgDim is a 2-element array containing the image x and y dimensions, or it may be omitted or null; it can be used to set a new image size at the same time the image is updated, if applicable.
// if "twostage" is true, the first load will occur immediately, and the return value will be a function
// that takes a boolean parameter (true to proceed with the 2nd load (including the blank-and-reload procedure), false to cancel) and an optional updated imgDim.
// This allows you to do the first load early... for example during an upload (to the server) of the image you want to (then) refresh.
function forceImgReload(src, isCrossDomain, imgDim, twostage)
{
var blankList, step = 0, // step: 0 - started initial load, 1 - wait before proceeding (twostage mode only), 2 - started forced reload, 3 - cancelled
iframe = window.document.createElement("iframe"), // Hidden iframe, in which to perform the load+reload.
loadCallback = function(e) // Callback function, called after iframe load+reload completes (or fails).
{ // Will be called TWICE unless twostage-mode process is cancelled. (Once after load, once after reload).
if (!step) // initial load just completed. Note that it doesn't actually matter if this load succeeded or not!
{
if (twostage) step = 1; // wait for twostage-mode proceed or cancel; don't do anything else just yet
else { step = 2; blankList = imgReloadBlank(src); iframe.contentWindow.location.reload(true); } // initiate forced-reload
}
else if (step===2) // forced re-load is done
{
imgReloadRestore(src,blankList,imgDim,(e||window.event).type==="error"); // last parameter checks whether loadCallback was called from the "load" or the "error" event.
if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
}
}
iframe.style.display = "none";
window.parent.document.body.appendChild(iframe); // NOTE: if this is done AFTER setting src, Firefox MAY fail to fire the load event!
iframe.addEventListener("load",loadCallback,false);
iframe.addEventListener("error",loadCallback,false);
iframe.src = (isCrossDomain ? "/echoimg.php?src="+encodeURIComponent(src) : src); // If src is cross-domain, script will crash unless we embed the image in a same-domain html page (using server-side script)!!!
return (twostage
? function(proceed,dim)
{
if (!twostage) return;
twostage = false;
if (proceed)
{
imgDim = (dim||imgDim); // overwrite imgDim passed in to forceImgReload() - just in case you know the correct img dimensions now, but didn't when forceImgReload() was called.
if (step===1) { step = 2; blankList = imgReloadBlank(src); iframe.contentWindow.location.reload(true); }
}
else
{
step = 3;
if (iframe.contentWindow.stop) iframe.contentWindow.stop();
if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
}
}
: null);
}
Luego, para forzar una actualización de una imagen ubicada en el mismo dominio que su página, puede hacer lo siguiente:
forceImgReload("myimage.jpg");
Para actualizar una imagen desde otro lugar (dominio cruzado):
forceImgReload("http://someother.server.com/someimage.jpg", true);
Una aplicación más avanzada podría ser volver a cargar una imagen después de cargar una nueva versión en su servidor, preparando la etapa inicial del proceso de recarga simultáneamente con la carga, para minimizar el retraso de recarga visible para el usuario. Si está cargando a través de AJAX, y el servidor está devolviendo una matriz JSON muy simple [éxito, ancho, altura], entonces su código podría verse así:
// fileForm is a reference to the form that has a the <input typ="file"> on it, for uploading.
// serverURL is the url at which the uploaded image will be accessible from, once uploaded.
// The response from uploadImageToServer.php is a JSON array [success, width, height]. (A boolean and two ints).
function uploadAndRefreshCache(fileForm, serverURL)
{
var xhr = new XMLHttpRequest(),
proceedWithImageRefresh = forceImgReload(serverURL, false, null, true);
xhr.addEventListener("load", function(){ var arr = JSON.parse(xhr.responseText); if (!(arr&&arr[0])) { proceedWithImageRefresh(false); doSomethingOnUploadFailure(...); } else { proceedWithImageRefresh(true,[arr[1],ar[2]]); doSomethingOnUploadSuccess(...); }});
xhr.addEventListener("error", function(){ proceedWithImageRefresh(false); doSomethingOnUploadError(...); });
xhr.addEventListener("abort", function(){ proceedWithImageRefresh(false); doSomethingOnUploadAborted(...); });
// add additional event listener(s) to track upload progress for graphical progress bar, etc...
xhr.open("post","uploadImageToServer.php");
xhr.send(new FormData(fileForm));
}
Una nota final: aunque este tema trata sobre imágenes, también se aplica potencialmente a otros tipos de archivos o recursos. Por ejemplo, evitar el uso de secuencias de comandos obsoletas o archivos CSS, o quizás incluso actualizar documentos PDF actualizados (usando (4) solo si está configurado para abrir en el navegador). El método (4) puede requerir algunos cambios en el javascript anterior, en estos casos.