Eu já vi muitas variações de respostas sobre como fazer isso, então pensei em resumi-las aqui (além de adicionar um quarto método de minha própria invenção):
(1) Adicione um parâmetro exclusivo de consulta de impedimento de cache ao URL, como:
newImage.src = "image.jpg?t=" + new Date().getTime();
Prós: 100% confiável, rápido e fácil de entender e implementar.
Contras: ignora completamente o cache, significando atrasos desnecessários e uso de largura de banda sempre que a imagem não muda entre as visualizações. Potencialmente preencherá o cache do navegador (e quaisquer caches intermediários) com muitas, muitas cópias da mesma imagem! Além disso, requer a modificação do URL da imagem.
Quando usar: use quando a imagem estiver mudando constantemente, como para um feed de webcam ao vivo. Se você usar esse método, certifique-se de veicular as imagens com Cache-control: no-cache
cabeçalhos HTTP !!! (Geralmente, isso pode ser configurado usando um arquivo .htaccess). Caso contrário, você estará progressivamente preenchendo caches com versões antigas da imagem!
(2) Adicione um parâmetro de consulta ao URL que muda apenas quando o arquivo é alterado, por exemplo:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(Esse é o código do lado do servidor PHP, mas o ponto importante aqui é apenas que uma string de consulta ? M = [hora da última modificação do arquivo] é anexada ao nome do arquivo).
Prós: 100% confiável, rápido e fácil de entender e implementar, e preserva perfeitamente as vantagens do cache.
Contras: Requer a modificação do URL da imagem. Além disso, um pouco mais de trabalho para o servidor - ele precisa ter acesso ao horário da última modificação do arquivo. Além disso, exige informações do lado do servidor, portanto, não é adequado para uma solução exclusivamente do lado do cliente verificar uma imagem atualizada.
Quando usar: quando você deseja armazenar em cache imagens, mas pode precisar atualizá-las periodicamente no servidor, sem alterar o nome do arquivo. E quando você pode garantir facilmente que a string de consulta correta seja adicionada a cada instância de imagem no seu HTML.
(3) Sirva suas imagens com o cabeçalho Cache-control: max-age=0, must-revalidate
e adicione à URL um identificador de fragmento exclusivo para armazenamento de memcache , como:
newImage.src = "image.jpg#" + new Date().getTime();
A idéia aqui é que o cabeçalho de controle de cache coloque imagens no cache do navegador, mas imediatamente as coloque obsoletas, para que, e sempre que sejam exibidas novamente, o navegador verifique com o servidor se elas foram alteradas. Isso garante que o cache HTTP do navegador sempre retorne a cópia mais recente da imagem. No entanto, os navegadores geralmente reutilizam uma cópia na memória de uma imagem, se tiverem uma, e nem mesmo verificam seu cache HTTP nesse caso. Para evitar isso, um identificador de fragmento é usado: A comparação das imagens na memória src
inclui o identificador de fragmento, mas ele é retirado antes de consultar o cache HTTP. (Por exemplo, image.jpg#A
e image.jpg#B
podem ser exibidos a partir da image.jpg
entrada no cache HTTP do navegador, masimage.jpg#B
nunca seria exibido usando dados de imagem retidos na memória desde a image.jpg#A
última vez que foi exibido).
Prós: faz uso adequado dos mecanismos de cache HTTP e usa imagens em cache se elas não foram alteradas. Funciona para servidores que engasgam com uma string de consulta adicionada a uma URL de imagem estática (já que os servidores nunca veem identificadores de fragmento - eles são apenas para uso do próprio navegador).
Contras: Depende do comportamento um tanto duvidoso (ou pelo menos mal documentado) dos navegadores em relação às imagens com identificadores de fragmentos em seus URLs (no entanto, testei isso com êxito no FF27, Chrome33 e IE11). Ainda envia uma solicitação de revalidação ao servidor para cada exibição de imagem, o que pode ser um exagero se as imagens forem alteradas raramente e / ou a latência for um grande problema (pois é necessário aguardar a resposta de revalidação, mesmo quando a imagem em cache ainda estiver boa) . Requer a modificação de URLs de imagem.
Quando usar: use quando as imagens podem mudar com freqüência ou precisam ser atualizadas de forma intermitente pelo cliente, sem o envolvimento do script do servidor, mas onde você ainda deseja a vantagem do armazenamento em cache. Por exemplo, pesquisar uma webcam ao vivo que atualiza uma imagem irregularmente a cada poucos minutos. Como alternativa, use em vez de (1) ou (2) se o servidor não permitir cadeias de consulta em URLs de imagem estática.
(4) Atualize à força uma imagem específica usando Javascript, primeiro carregando-a em uma oculta <iframe>
e depois chamando location.reload(true)
a iframe contentWindow
.
Os passos são:
Carregue a imagem a ser atualizada em um iframe oculto. Esta é apenas uma etapa da configuração - pode ser feita com muito antecedência a atualização real, se desejado. Não importa se a imagem falha ao carregar nesta fase!
Feito isso, esvazie todas as cópias dessa imagem em suas páginas ou em qualquer lugar em qualquer nó DOM (mesmo fora da página armazenado em variáveis javascript). Isso é necessário porque o navegador pode exibir a imagem de uma cópia antiga na memória (o IE11 faz isso especialmente): É necessário garantir que todas as cópias na memória sejam limpas antes de atualizar o cache HTTP. Se outro código javascript estiver sendo executado de forma assíncrona, talvez você também precise impedir que esse código crie novas cópias da imagem a ser atualizada enquanto isso.
Ligue iframe.contentWindow.location.reload(true)
. As true
forças de um desvio cache, recarregar diretamente do servidor e substituindo a cópia em cache existente.
Assim que terminar re -loading, restaurar as imagens em branco. Agora eles devem exibir a versão nova do servidor!
Para imagens do mesmo domínio, você pode carregar a imagem diretamente no iframe. Para imagens entre domínios, você deve carregar uma página HTML do seu domínio que contenha a imagem em uma <img>
tag, caso contrário, você receberá um erro "Acesso negado" ao tentar ligar iframe.contentWindow.reload(...)
.
Prós: Funciona exatamente como a função image.reload () que você deseja que o DOM possua ! Permite que as imagens sejam armazenadas em cache normalmente (mesmo com datas de validade futuras, se você as desejar, evitando assim a revalidação frequente). Permite atualizar uma imagem específica sem alterar os URLs dessa imagem na página atual ou em qualquer outra página, usando apenas o código do lado do cliente.
Contras: depende de Javascript. Não é 100% garantido que funcione corretamente em todos os navegadores (embora eu tenha testado isso com êxito no FF27, Chrome33 e IE11). Muito complicado em relação aos outros métodos.
Quando usar: quando você tem uma coleção de imagens basicamente estáticas que deseja armazenar em cache, mas ainda precisa atualizá-las ocasionalmente e obter feedback visual imediato de que a atualização ocorreu. (Especialmente quando apenas atualizar a página inteira do navegador não funcionaria, como em alguns aplicativos da Web criados no AJAX, por exemplo). E quando os métodos (1) - (3) não são viáveis, porque (por qualquer motivo) você não pode alterar todos os URLs que podem exibir a imagem que você precisa atualizar. (Observe que, usando esses três métodos, a imagem será atualizada, mas se outra página tentar exibir essa imagem sem a string de consulta ou o identificador de fragmento apropriado, ela poderá mostrar uma versão mais antiga).
Os detalhes de implementar isso de uma maneira robusta e flexível são dados abaixo:
Vamos supor que seu site contenha .gif em branco de 1x1 pixel no caminho da URL /img/1x1blank.gif
e também tenha o seguinte script PHP de uma linha (necessário apenas para aplicar atualização forçada a imagens entre domínios) e pode ser reescrito em qualquer linguagem de script do lado do servidor , é claro) no caminho da URL /echoimg.php
:
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
Então, aqui está uma implementação realista de como você pode fazer tudo isso em Javascript. Parece um pouco complicado, mas há muitos comentários, e a função importante é apenas forceImgReload () - as duas primeiras imagens em branco e não em branco, e devem ser projetadas para funcionar eficientemente com seu próprio HTML, codifique-as como funciona melhor para você; muitas das complicações nelas podem ser desnecessárias para o seu site:
// 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);
}
Em seguida, para forçar uma atualização de uma imagem localizada no mesmo domínio da sua página, você pode:
forceImgReload("myimage.jpg");
Para atualizar uma imagem de outro lugar (entre domínios):
forceImgReload("http://someother.server.com/someimage.jpg", true);
Um aplicativo mais avançado pode ser recarregar uma imagem após o upload de uma nova versão para o servidor, preparando o estágio inicial do processo de recarregamento simultaneamente com o upload, para minimizar o atraso visível do recarregamento para o usuário. Se você estiver fazendo o upload via AJAX, e o servidor estiver retornando uma matriz JSON muito simples [sucesso, largura, altura], seu código poderá se parecer com o seguinte:
// 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));
}
Uma observação final: embora este tópico seja sobre imagens, ele também se aplica a outros tipos de arquivos ou recursos. Por exemplo, impedindo o uso de scripts obsoletos ou arquivos css, ou mesmo atualizando documentos PDF atualizados (usando (4) somente se configurado para abrir no navegador). O método (4) pode exigir algumas alterações no javascript acima, nesses casos.