Widziałem wiele różnych odpowiedzi na pytanie, jak to zrobić, więc pomyślałem, że streszczę je tutaj (plus dodaję czwartą metodę mojego wynalazku):
(1) Dodaj unikalny parametr zapytania pomijania pamięci podręcznej do adresu URL, taki jak:
newImage.src = "image.jpg?t=" + new Date().getTime();
Plusy: 100% niezawodny, szybki i łatwy do zrozumienia i wdrożenia.
Wady: całkowicie pomija buforowanie, co oznacza niepotrzebne opóźnienia i wykorzystanie przepustowości, gdy obraz nie zmienia się między widokami. Potencjalnie zapełni pamięć podręczną przeglądarki (i wszelkie pośrednie pamięci podręczne) wieloma, wieloma kopiami dokładnie tego samego obrazu! Wymaga również modyfikacji adresu URL obrazu.
Kiedy stosować: Użyj, gdy obraz ciągle się zmienia, na przykład w przypadku obrazu z kamery na żywo. Jeśli korzystasz z tej metody, pamiętaj, aby podawać same obrazy z Cache-control: no-cache
nagłówkami HTTP !!! (Często można to skonfigurować za pomocą pliku .htaccess). W przeciwnym razie będziesz stopniowo zapełniał pamięć podręczną starymi wersjami obrazu!
(2) Dodaj parametr zapytania do adresu URL, który zmienia się tylko wtedy, gdy plik się zmienia, np .:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(To jest kod PHP po stronie serwera, ale ważne jest tutaj to, że do nazwy pliku dołączany jest kwerenda ? M = [czas ostatniej modyfikacji pliku]).
Plusy: w 100% niezawodny, szybki i łatwy do zrozumienia i wdrożenia oraz doskonale zachowuje zalety buforowania.
Cons: Wymaga modyfikacji adresu URL obrazu. Trochę więcej pracy dla serwera - musi on uzyskać dostęp do czasu ostatniej modyfikacji pliku. Wymaga również informacji po stronie serwera, więc nie nadaje się do rozwiązania wyłącznie po stronie klienta, aby sprawdzić odświeżony obraz.
Kiedy użyć: Kiedy chcesz buforować obrazy, ale może wymagać aktualizacji od czasu do czasu na serwerze bez zmiany samej nazwy pliku. ORAZ kiedy możesz łatwo upewnić się, że do każdego wystąpienia obrazu w kodzie HTML dodano poprawne zapytanie.
(3) Podawać swoje zdjęcia z nagłówkiem Cache-control: max-age=0, must-revalidate
i dodać unikatowy memcache -busting fragment identyfikator do adresu URL, takich jak:
newImage.src = "image.jpg#" + new Date().getTime();
Chodzi o to, że nagłówek kontroli pamięci podręcznej umieszcza obrazy w pamięci podręcznej przeglądarki, ale natychmiast zaznacza je jako przestarzałe, więc za każdym razem, gdy są ponownie wyświetlane, przeglądarka musi sprawdzić na serwerze, czy się zmieniły. Zapewnia to, że pamięć podręczna HTTP przeglądarki zawsze zwraca najnowszą kopię obrazu. Jednak przeglądarki często używają kopii obrazu w pamięci, jeśli go mają, i nawet nie sprawdzają pamięci podręcznej HTTP w tym przypadku. Aby temu zapobiec, stosuje się identyfikator fragmentu: Porównanie obrazu w pamięci src
obejmuje identyfikator fragmentu, ale zostaje on usunięty przed zapytaniem o pamięć podręczną HTTP. (Tak więc, na przykład, image.jpg#A
i image.jpg#B
oba mogą być wyświetlane zimage.jpg
pozycji w pamięci podręcznej HTTP przeglądarki, aleimage.jpg#B
nigdy nie będzie wyświetlany przy użyciu danych obrazu zachowanych w pamięci od czasu image.jpg#A
ostatniego wyświetlenia).
Plusy: Właściwie wykorzystuje mechanizmy buforowania HTTP i używa buforowanych obrazów, jeśli się nie zmieniły. Działa na serwerach, które dławią się kwerendą dodawaną do statycznego adresu URL obrazu (ponieważ serwery nigdy nie widzą identyfikatorów fragmentów - są one wyłącznie na użytek przeglądarki).
Cons: Polega na nieco wątpliwym (lub przynajmniej słabo udokumentowanym) zachowaniu przeglądarek w odniesieniu do obrazów z identyfikatorami fragmentów w ich adresach URL (Jednak przetestowałem to z powodzeniem w FF27, Chrome33 i IE11). Nadal wysyła do serwera żądanie ponownej walidacji dla każdego widoku obrazu, co może być nadmierne, jeśli obrazy zmieniają się rzadko i / lub opóźnienie jest dużym problemem (ponieważ musisz poczekać na odpowiedź na ponowne sprawdzenie poprawności, nawet jeśli obraz w pamięci podręcznej jest nadal dobry) . Wymaga modyfikacji adresów URL obrazów.
Kiedy stosować: Użyj, gdy obrazy mogą się często zmieniać lub wymagają okresowego odświeżania przez klienta bez udziału skryptu po stronie serwera, ale tam, gdzie nadal chcesz korzystać z buforowania. Na przykład odpytywanie kamery internetowej na żywo, która co kilka minut aktualizuje obraz nieregularnie. Alternatywnie, użyj zamiast (1) lub (2), jeśli twój serwer nie pozwala na kwerendy na statycznych adresach URL obrazów.
(4) Wymuś odświeżenie określonego obrazu za pomocą Javascript, najpierw ładując go do ukrytego, <iframe>
a następnie wywołując location.reload(true)
ramkę iframe contentWindow
.
Kroki są następujące:
Załaduj obraz do odświeżenia do ukrytego elementu iframe. To tylko krok konfiguracji - w razie potrzeby można to zrobić z dużym wyprzedzeniem. Nie ma nawet znaczenia, czy obraz nie załaduje się na tym etapie!
Po zakończeniu wyczyść wszystkie kopie tego obrazu na swojej stronie (stronach) lub w dowolnym miejscu w dowolnym węźle DOM (nawet poza stroną przechowywaną w zmiennych javascript). Jest to konieczne, ponieważ w przeciwnym razie przeglądarka może wyświetlić obraz z czerstwego kopii w pamięci (IE11 szczególnie robi to): Trzeba zapewnić wszystkim w pamięci kopie są usuwane, zanim orzeźwiający HTTP cache. Jeśli inny kod javascript działa asynchronicznie, być może trzeba będzie w międzyczasie uniemożliwić temu kodowi tworzenie nowych kopii obrazu, który ma zostać odświeżony.
Zadzwoń iframe.contentWindow.location.reload(true)
. Te true
siły obejście cache, przeładunek bezpośrednio z serwera i nadpisania istniejącej kopii buforowane.
Po zakończeniu ponownego ładowania przywróć puste obrazy. Powinny teraz wyświetlać nową wersję z serwera!
W przypadku obrazów z tej samej domeny możesz załadować obraz bezpośrednio do iframe. W przypadku obrazów między domenami należy zamiast tego załadować stronę HTML z domeny zawierającą obraz w <img>
tagu, w przeciwnym razie podczas próby połączenia zostanie wyświetlony błąd „Odmowa dostępu” iframe.contentWindow.reload(...)
.
Plusy: Działa tak jak funkcja image.reload (), którą chciałbyś, aby DOM miał! Pozwala na normalne buforowanie obrazów (nawet z przyszłymi datami ważności, jeśli chcesz, co pozwala uniknąć częstego przedłużania ważności). Umożliwia odświeżenie określonego obrazu bez zmiany adresów URL tego obrazu na bieżącej stronie lub na innych stronach, przy użyciu tylko kodu po stronie klienta.
Minusy: opiera się na JavaScript. Nie gwarantujemy 100% poprawnego działania w każdej przeglądarce (przetestowałem to z powodzeniem w FF27, Chrome33 i IE11). Bardzo skomplikowane w stosunku do innych metod.
Kiedy używać: Gdy masz kolekcję zasadniczo statycznych obrazów, które chcesz buforować, ale nadal musisz mieć możliwość ich okresowej aktualizacji i natychmiastowej wizualnej informacji zwrotnej, że aktualizacja miała miejsce. (Zwłaszcza gdy odświeżanie całej strony przeglądarki nie działa, jak w niektórych aplikacjach internetowych opartych na AJAX na przykład). A gdy metody (1) - (3) są niewykonalne, ponieważ (z jakiegokolwiek powodu) nie można zmienić wszystkich adresów URL, które mogą potencjalnie wyświetlać obraz, który należy zaktualizować. (Zauważ, że przy użyciu tych 3 metod obraz zostanie odświeżony, ale jeśli inna strona następnie spróbuje wyświetlić ten obraz bez odpowiedniego zapytania lub identyfikatora fragmentu, może zamiast tego wyświetlić starszą wersję).
Szczegóły implementacji tego w dość solidny i elastyczny sposób podano poniżej:
Załóżmy, że twoja strona zawiera pusty 1x1 piksel .gif w ścieżce URL /img/1x1blank.gif
, a także ma następujący jednowierszowy skrypt PHP (wymagany tylko do zastosowania wymuszonego odświeżania obrazów między domenami i może być przepisany w dowolnym języku skryptowym po stronie serwera , oczywiście) w ścieżce URL /echoimg.php
:
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
Oto realistyczna implementacja tego, jak możesz to wszystko zrobić w JavaScript. Wygląda to trochę skomplikowane, ale jest wiele komentarzy, a ważną funkcją jest po prostu forceImgReload () - pierwsze dwa tylko puste i niepuste obrazy, i powinny być zaprojektowane do wydajnej pracy z własnym HTML, więc koduj je jako działa najlepiej dla ciebie; wiele z ich komplikacji może być niepotrzebnych dla Twojej witryny:
// 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);
}
Następnie, aby wymusić odświeżenie obrazu znajdującego się w tej samej domenie co strona, możesz po prostu:
forceImgReload("myimage.jpg");
Aby odświeżyć obraz z innego miejsca (między domenami):
forceImgReload("http://someother.server.com/someimage.jpg", true);
Bardziej zaawansowaną aplikacją może być przeładowanie obrazu po przesłaniu nowej wersji na serwer, przygotowanie wstępnego etapu procesu przeładowania jednocześnie z przesyłaniem, aby zminimalizować widoczne opóźnienie przeładowania dla użytkownika. Jeśli przesyłasz za pośrednictwem AJAX, a serwer zwraca bardzo prostą tablicę JSON [sukces, szerokość, wysokość], wówczas twój kod może wyglądać mniej więcej tak:
// 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));
}
Ostatnia uwaga: chociaż ten temat dotyczy obrazów, potencjalnie dotyczy również innych rodzajów plików lub zasobów. Na przykład zapobieganie używaniu przestarzałych plików skryptów lub plików css, a może nawet odświeżanie zaktualizowanych dokumentów PDF (przy użyciu (4) tylko w przypadku skonfigurowania do otwierania w przeglądarce). W takich przypadkach metoda (4) może wymagać pewnych zmian w powyższym javascript.