Ich habe viele unterschiedliche Antworten darauf gesehen, wie man das macht, also dachte ich, ich fasse sie hier zusammen (und füge eine vierte Methode meiner eigenen Erfindung hinzu):
(1) Fügen Sie der URL einen eindeutigen Cache-Busting-Abfrageparameter hinzu, z. B:
newImage.src = "image.jpg?t=" + new Date().getTime();
Vorteile: 100% zuverlässig, schnell und einfach zu verstehen und umzusetzen.
Nachteile: Umgeht die Zwischenspeicherung vollständig, was unnötige Verzögerungen und Bandbreitennutzung bedeutet, wenn das Bild nicht zwischen den Ansichten wechseln. Füllt möglicherweise den Browser-Cache (und etwaige Zwischencaches) mit vielen, vielen Kopien genau desselben Bildes! Außerdem muss die Bild-URL geändert werden.
Wann zu verwenden: Wird verwendet, wenn sich das Bild ständig ändert, z. B. bei einer Live-Webcam. Wenn Sie diese Methode verwenden, Stellen Sie sicher, dass Sie die Bilder selbst mit Cache-control: no-cache
HTTP-Header!!! (Oft kann dies mit einer .htaccess-Datei eingerichtet werden). Andernfalls werden Sie nach und nach die Caches mit alten Versionen des Bildes füllen!
(2) Fügen Sie der URL einen Abfrageparameter hinzu, der sich nur ändert, wenn sich die Datei ändert, z. B.:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(Das ist PHP-Server-seitiger Code, aber der wichtige Punkt hier ist nur, dass ein ?m= [Datei zuletzt geänderte Zeit] querystring wird an den Dateinamen angehängt).
Vorteile: 100% zuverlässig, schnell und einfach zu verstehen und umzusetzen, et bewahrt die Vorteile der Zwischenspeicherung perfekt.
Nachteile: Erfordert die Änderung der Bild-URL. Außerdem muss der Server etwas mehr Arbeit leisten, da er Zugriff auf den Zeitpunkt der letzten Änderung der Datei erhalten muss. Erfordert außerdem serverseitige Informationen und ist daher nicht für eine rein clientseitige Lösung geeignet, um nach einem aktualisierten Bild zu suchen.
Wann zu verwenden: Wenn Sie Bilder zwischenspeichern möchten, diese aber möglicherweise von Zeit zu Zeit auf dem Server aktualisieren müssen, ohne den Dateinamen selbst zu ändern. UND wenn Sie einfach sicherstellen können, dass der richtige Querystring zu jeder Bildinstanz in Ihrem HTML-Code hinzugefügt wird.
(3) Servieren Sie Ihre Bilder mit der Kopfzeile Cache-control: max-age=0, must-revalidate
und fügen Sie eine eindeutige memcache -Fragmentbezeichner an die URL anhängen, z. B.:
newImage.src = "image.jpg#" + new Date().getTime();
Die Idee dahinter ist, dass der cache-control-Header Bilder in den Browser-Cache stellt, sie aber sofort als veraltet markiert, so dass der Browser bei jeder erneuten Anzeige beim Server nachfragen muss, ob sie sich geändert haben. Dadurch wird sichergestellt, dass der Browser die HTTP-Cache gibt immer die letzte Kopie des Bildes zurück. Browser verwenden jedoch oft eine speicherinterne Kopie eines Bildes wieder, wenn sie eine haben, und prüfen in diesem Fall nicht einmal ihren HTTP-Cache. Um dies zu verhindern, wird ein Fragment-Bezeichner verwendet: Vergleich des In-Memory-Bildes src
enthält den Fragmentbezeichner, der jedoch vor der Abfrage des HTTP-Caches entfernt wird. (Also, z.B., image.jpg#A
y image.jpg#B
können beide in der image.jpg
Eintrag im HTTP-Cache des Browsers, aber image.jpg#B
würde niemals unter Verwendung von im Speicher gehaltenen Bilddaten angezeigt werden, wenn image.jpg#A
zuletzt angezeigt wurde).
Vorteile: Nutzt die HTTP-Caching-Mechanismen und verwendet zwischengespeicherte Bilder, wenn sie sich nicht geändert haben. Funktioniert bei Servern, die einen Querystring, der einer statischen Bild-URL hinzugefügt wurde, nicht verarbeiten können (da Server niemals Fragmentbezeichner sehen - sie sind nur für den eigenen Gebrauch des Browsers bestimmt).
Nachteile: Verlässt sich auf ein etwas zweifelhaftes (oder zumindest schlecht dokumentiertes) Verhalten von Browsern in Bezug auf Bilder mit Fragmentbezeichnern in ihren URLs (ich habe dies jedoch erfolgreich in FF27, Chrome33 und IE11 getestet). Es wird immer noch bei jeder Bildansicht eine Überprüfungsanfrage an den Server gesendet, was zu viel sein kann, wenn sich Bilder nur selten ändern und/oder Latenz ein großes Problem ist (da man auf die Überprüfungsantwort warten muss, auch wenn das Bild im Cache noch gut ist). Erfordert die Änderung von Bild-URLs.
Wann zu verwenden: Verwenden Sie diese Option, wenn sich Bilder häufig ändern oder vom Client ohne Beteiligung eines serverseitigen Skripts zeitweise aktualisiert werden müssen, Sie aber dennoch die Vorteile des Caching nutzen möchten. Zum Beispiel bei der Abfrage einer Live-Webcam, die ein Bild in unregelmäßigen Abständen alle paar Minuten aktualisiert. Alternativ können Sie auch (1) oder (2) verwenden, wenn Ihr Server keine Querystrings für statische Bild-URLs zulässt.
[EDIT 2021: Funktioniert nicht mehr auf neueren Browsern wie Chrome und Edge: Der interne Memcache in diesen Browsern ignoriert jetzt Fragmentbezeichner (vielleicht seit dem Wechsel zur Blink-Engine?). Aber siehe Methode (4) unten, es ist jetzt VIEL einfacher auf diesen beiden Browsern speziell, so erwägen, diese Methode mit einer vereinfachten Version von (4) zu kombinieren, um diese beiden Browser abzudecken].
(4) Erzwingen Sie die Aktualisierung eines bestimmten Bildes mit Javascript, indem Sie es zunächst in einen versteckten <iframe>
und ruft dann location.reload(true)
auf der iframe-Seite contentWindow
.
Die Schritte sind:
-
Laden Sie das zu aktualisierende Bild in einen versteckten iframe. [EDIT 2021: Für Chrome und Edge laden Sie eine HTML-Seite mit einem <img>
Tag, nicht die Rohbilddatei]. Dies ist nur ein Einrichtungsschritt - er kann lange vor der eigentlichen Aktualisierung durchgeführt werden, falls gewünscht. Es macht nicht einmal etwas aus, wenn das Bild in dieser Phase nicht geladen werden kann!
-
[EDIT 2021: Dieser Schritt ist in neueren Chrome- und Edge-Versionen nicht mehr erforderlich]. Sobald das geschehen ist, löschen Sie alle Kopien dieses Bildes auf Ihrer Seite oder irgendwo in einem DOM-Knoten (auch außerhalb der Seite in Javascript-Variablen). Dies ist notwendig, weil der Browser das Bild sonst möglicherweise aus einer veralteten Kopie im Speicher anzeigt (insbesondere IE11 tut dies): Sie müssen sicherstellen, dass alle speicherintern Kopien geleert werden, bevor der HTTP-Cache aktualisiert wird. Wenn anderer Javascript-Code asynchron ausgeführt wird, müssen Sie möglicherweise auch verhindern, dass dieser Code in der Zwischenzeit neue Kopien des zu aktualisierenden Bildes erstellt.
-
Rufen Sie an. iframe.contentWindow.location.reload(true)
. Die true
erzwingt eine Umgehung des Cache, indem sie direkt vom Server neu geladen wird und die vorhandene Kopie im Cache überschreibt.
-
[EDIT 2021: Dieser Schritt ist in neueren Browsern wie Chrome und Edge überflüssig - dort aktualisieren sich die vorhandenen Bilder nach dem vorherigen Schritt automatisch]. Sobald es fertig ist zu -Laden, Wiederherstellen der ausgeblendeten Bilder. Sie sollten nun die frische Version vom Server anzeigen!
Bei Bildern, die von derselben Domain stammen, können Sie das Bild direkt in den iframe laden. [EDIT 2021: Nicht auf Chrome, Edge]. Für domänenübergreifende Bilder müssen Sie stattdessen eine HTML-Seite laden von Ihrer Domäne die das Bild in einer <img>
Tag, sonst erhalten Sie eine Fehlermeldung "Zugriff verweigert", wenn Sie versuchen, die iframe.contentWindow.reload(...)
. [Tun Sie dies auch für Chrome und Edge].
Vorteile: Funktioniert genau wie die Funktion image.reload(), die Sie Wunsch das DOM hatte! Ermöglicht die normale Zwischenspeicherung von Bildern (sogar mit zukünftigen Verfallsdaten, wenn Sie dies wünschen, wodurch eine häufige erneute Überprüfung vermieden wird). Ermöglicht es Ihnen, ein bestimmtes Bild zu aktualisieren, ohne die URLs für dieses Bild auf der aktuellen Seite oder auf anderen Seiten zu ändern, indem Sie nur Client-seitigen Code verwenden.
Nachteile: Setzt auf Javascript. Es gibt keine 100%ige Garantie, dass es in jedem Browser funktioniert (ich habe es jedoch erfolgreich in FF27, Chrome33 und IE11 getestet). Sehr kompliziert im Vergleich zu den anderen Methoden. [EDIT 2021: Es sei denn, Sie benötigen nur aktuelle Chrome- und Edge-Unterstützung, in diesem Fall ist es sehr viel einfacher].
Wann zu verwenden: Wenn Sie eine Sammlung von im Grunde statischen Bildern haben, die Sie zwischengespeichert haben möchten, aber dennoch in der Lage sein müssen, sie gelegentlich zu aktualisieren und eine sofortige visuelle Rückmeldung zu erhalten, dass die Aktualisierung stattgefunden hat. (Vor allem, wenn ein einfaches Aktualisieren der gesamten Browserseite nicht möglich ist, wie z. B. bei einigen Webanwendungen, die auf AJAX basieren). Und wenn die Methoden (1)-(3) nicht durchführbar sind, weil Sie (aus welchen Gründen auch immer) nicht alle URLs ändern können, die das zu aktualisierende Bild möglicherweise anzeigen. (Beachten Sie, dass mit diesen 3 Methoden das Bild aktualisiert wird, aber wenn eine andere Seite versucht dann, dieses Bild anzuzeigen ohne den entsprechenden Querystring oder Fragment-Identifikator, kann es stattdessen eine ältere Version anzeigen).
Im Folgenden wird erläutert, wie dies auf eine robuste und flexible Weise umgesetzt werden kann:
Nehmen wir an, Ihre Website enthält ein leeres 1x1 Pixel großes .gif im URL-Pfad /img/1x1blank.gif
und enthält außerdem das folgende einzeilige PHP-Skript (nur erforderlich für die Anwendung der erzwungenen Aktualisierung auf domänenübergreifend Bilder, und kann natürlich in jeder serverseitigen Skriptsprache umgeschrieben werden) unter dem URL-Pfad /echoimg.php
:
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
Dann gibt es hier eine realistische Umsetzung, wie Sie all dies in Javascript tun könnten. Es sieht ein bisschen kompliziert aus, aber es gibt eine Menge Kommentare, und die wichtige Funktion ist nur forceImgReload() - die ersten beiden nur leer und un-blank Bilder, und sollte entworfen werden, um effizient mit Ihrem eigenen HTML arbeiten, so Code sie wie funktioniert am besten für Sie; viel von der Komplikationen in ihnen kann für Ihre Website unnötig sein:
// 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);
}
Um eine Aktualisierung eines Bildes zu erzwingen, das sich auf derselben Domain wie Ihre Seite befindet, können Sie einfach Folgendes tun:
forceImgReload("myimage.jpg");
Um ein Bild von einem anderen Ort zu aktualisieren (domänenübergreifend):
forceImgReload("http://someother.server.com/someimage.jpg", true);
Eine fortschrittlichere Anwendung könnte darin bestehen, ein Bild nach dem Hochladen einer neuen Version auf Ihren Server neu zu laden, wobei die Anfangsphase des Neuladevorgangs gleichzeitig mit dem Hochladen vorbereitet wird, um die sichtbare Verzögerung beim Neuladen für den Benutzer zu minimieren. Wenn Sie den Upload über AJAX durchführen und der Server ein sehr einfaches JSON-Array [Erfolg, Breite, Höhe] zurückgibt, könnte Ihr Code etwa so aussehen:
// 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));
}
Ein letzter Hinweis: Obwohl sich dieses Thema auf Bilder bezieht, gilt es möglicherweise auch für andere Arten von Dateien oder Ressourcen. Zum Beispiel, um die Verwendung von veralteten Skript- oder CSS-Dateien zu verhindern, oder vielleicht sogar um aktualisierte PDF-Dokumente zu aktualisieren (mit (4) nur, wenn sie für das Öffnen im Browser eingerichtet sind). Methode (4) könnte in diesen Fällen einige Änderungen am obigen Javascript erfordern.