Odśwież obraz z nowym pod tym samym adresem URL

333

Uzyskuję dostęp do linku w mojej witrynie, który zapewnia nowy obraz za każdym razem, gdy jest dostępny.

Problem, na który napotykam, polega na tym, że jeśli spróbuję załadować obraz w tle, a następnie zaktualizować ten na stronie, obraz się nie zmieni - chociaż jest aktualizowany po ponownym załadowaniu strony.

var newImage = new Image();
newImage.src = "http://localhost/image.jpg";

function updateImage()
{
if(newImage.complete) {
    document.getElementById("theText").src = newImage.src;
    newImage = new Image();
    number++;
    newImage.src = "http://localhost/image/id/image.jpg?time=" + new Date();
}

    setTimeout(updateImage, 1000);
}

Nagłówki, jakie widzi FireFox:

HTTP/1.x 200 OK
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: image/jpeg
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Server: Microsoft-HTTPAPI/1.0
Date: Thu, 02 Jul 2009 23:06:04 GMT

Muszę wymusić odświeżenie tylko tego obrazu na stronie. Jakieś pomysły?

QueueHammer
źródło
Zobacz stackoverflow.com/questions/126772/…
Philibert Perusse

Odpowiedzi:

350

Spróbuj dodać bufor pamięci podręcznej na końcu adresu URL:

newImage.src = "http://localhost/image.jpg?" + new Date().getTime();

Spowoduje to automatyczne dodanie bieżącego znacznika czasu podczas tworzenia obrazu i sprawi, że przeglądarka ponownie wyszuka obraz zamiast pobierać ten z pamięci podręcznej.

Paolo Bergantino
źródło
26
Niezbyt dobre rozwiązanie, ponieważ spowoduje zalanie pamięci podręcznej (zarówno lokalnej, jak i górnej). Odpowiedź Aya ma lepszy sposób na poradzenie sobie z tym.
Tgr
1
Czy ponowne wyświetlenie tego samego obrazu w innym miejscu, bez późniejszego „przerywacza pamięci podręcznej”, nadal pokazuje starą wersję z pamięci podręcznej (przynajmniej w Firefox)? i # :(
T4NK3R
3
Dzięki temu możesz zrobić jeszcze mniej kodu:'image.jpg?' + (+new Date())
Lew Łukomsky
4
Jest Date.now()na to
vp_arth
2
Dlaczego nieMath.random()
Gowtham Gopalakrishnan
227

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-cachenagłó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-revalidatei 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 srcobejmuje identyfikator fragmentu, ale zostaje on usunięty przed zapytaniem o pamięć podręczną HTTP. (Tak więc, na przykład, image.jpg#Ai image.jpg#Boba mogą być wyświetlane zimage.jpg pozycji w pamięci podręcznej HTTP przeglądarki, aleimage.jpg#Bnigdy nie będzie wyświetlany przy użyciu danych obrazu zachowanych w pamięci od czasu image.jpg#Aostatniego 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 truesił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.

Doin
źródło
Podoba mi się pomysł metody 4, ale nie możesz załadować zawartości zewnętrznej za pomocą iframe, prawda? Obecnie używam metody 3 w jednostronnej aplikacji internetowej, ale nie podoba mi się to, że musisz przeładować całą stronę, aby uzyskać nowy obraz, nawet jeśli HTML szablonu zostanie ponownie załadowany.
Emilios1995,
@Emilios: ... Również nie rozumiem twojego komentarza o konieczności przeładowania całej strony. Obie metody (3) i (4) mogą być zaimplementowane w javascript po stronie klienta, bez przeładowywania czegokolwiek oprócz jednego odświeżanego obrazu. Dla metody (3) oznaczałoby to po prostu użycie javascript do zmiany właściwości „src” obrazu z (np.) image.jpg#123Na image.jpg#124(lub cokolwiek innego, o ile bit po „#” się zmieni). Czy możesz wyjaśnić, co to jest przeładowywanie i dlaczego?
Doin
@Emilios: Rzeczywiście możesz załadować zewnętrzną (międzydomenową) zawartość do ramki iframe ... ale nie możesz uzyskać do niej dostępu za contentWindowpomocą javascript, aby wykonać reload(true)wywołanie, które jest krytyczną częścią metody ... więc nie, metoda (4 ) nie będzie działać w przypadku treści między domenami. Dobrze zauważony; Zaktualizuję „Wady”, aby to uwzględnić.
Doin,
@Emilios: Ups, nie, nie zrobię: zdałem sobie sprawę, że prosta poprawka (teraz zawarta w mojej odpowiedzi) pozwala również na pracę z obrazami między domenami (pod warunkiem, że możesz umieścić skrypt na serwerze na serwerze).
Doin
@pseudosavant: Niestety zauważam to dopiero ~ 17 miesięcy później, ale przykro mi, że muszę ci powiedzieć, że twoje zmiany w moim kodzie zostały poważnie zepsute. (Szczerze mówiąc, nie sądzę, żeby kod zwrotny, który początkowo miałem, miał rację). Teraz napisałem obszernie część (4), zarówno wyjaśnienie, jak i kod. Twój poprzedni kod nigdy nie wygasał obrazów (więc może się nie powieść w dziwny sposób, szczególnie w IE, a zwłaszcza, jeśli obraz był pokazywany w wielu miejscach), ale co gorsza, usunął także iframe natychmiast po rozpoczęciu pełnego przeładowania, co prawdopodobnie oznaczało, że pracować tylko z przerwami lub wcale. Przepraszam!
Doin
185

Jako alternatywa dla ...

newImage.src = "http://localhost/image.jpg?" + new Date().getTime();

...wygląda na to że...

newImage.src = "http://localhost/image.jpg#" + new Date().getTime();

... wystarczy oszukać pamięć podręczną przeglądarki bez omijania pamięci podręcznej w górę, zakładając, że zwróciłeś prawidłowe Cache-Controlnagłówki. Chociaż możesz użyć ...

Cache-Control: no-cache, must-revalidate

... tracisz korzyści z nagłówków If-Modified-Sincelub If-None-Match, więc coś w stylu ...

Cache-Control: max-age=0, must-revalidate

... powinien uniemożliwić przeglądarce ponowne pobranie całego obrazu, jeśli tak naprawdę nie zmienił się. Testowane i działające na IE, Firefox i Chrome. Irytujące, że zawiedzie w Safari, chyba że użyjesz ...

Cache-Control: no-store

... chociaż nadal może to być lepsze niż zapełnianie pamięci podręcznej setkami identycznych obrazów, szczególnie gdy działają one na twoim serwerze. ;-)

Aktualizacja (28.09.2014): Obecnie wygląda na to, że Cache-Control: no-storejest także potrzebny w Chrome.

Aya
źródło
1
Świetny! Po długim czasie próbowania załadowania moich obrazów internetowych, które były ładowane z opóźnieniem, właśnie go rozwiązałem, stosując twoje rozwiązanie (z „#”, używanie „?” Nie działa dla mnie). Wielkie dzięki!!!
user304602
18
W grę wchodzą DWIE pamięci podręczne: jest tam zwykła pamięć podręczna HTTP przeglądarki oraz ostatnio wyświetlana pamięć podręczna obrazów. Ta ostatnia pamięć podręczna jest indeksowana przez pełny srcatrybut, więc dodanie unikalnego identyfikatora fragmentu gwarantuje, że obraz nie zostanie po prostu pobrany z pamięci. Ale identyfikatory fragmentów nie są wysyłane jako część żądań HTTP, więc zwykła pamięć podręczna HTTP będzie używana jak zwykle. Właśnie dlatego ta technika działa.
Doin
Istnieje kilka buforów nagłówka. właściwie nie znam zbyt dobrze angielskiego, czy możesz mi powiedzieć, którego powinienem użyć ?! Chcę czegoś, co nie buforuje tylko zmienionego zdjęcia (np. Captcha) i buforuje inne rzeczy. więc Cache-Control: max-age=0, must-revalidatejest dla mnie dobre?
Shafizadeh
To nie działa dla mnie. Jedyną różnicą w moim przypadku jest to, że mam adres URL akcji kontrolera, która pobiera img z db. Miałem inne argumenty za działaniem kontrolera, więc dodałem je jako „...... & convert = true & t =" + new Date (). GetTime (); oraz „...... & convert = true #” + new Date (). getTime () ;. Czy coś robię źle?
shaffooo
1
Aby uniknąć narzutu wynikającego z tworzenia obiektów i / lub wywoływania metod, można użyć inkrementującej liczby całkowitej jako newImage.src = "http://localhost/image.jpg#" + i++;
pomostu
7

Czy po utworzeniu nowego obrazu usuwasz stary obraz z DOM i zastępujesz go nowym?

Możesz pobierać nowe obrazy przy każdej aktualizacji obrazu, ale nie dodawać ich do strony.

Można to zrobić na wiele sposobów. Coś takiego działałoby.

function updateImage()
{
    var image = document.getElementById("theText");
    if(image.complete) {
        var new_image = new Image();
        //set up the new image
        new_image.id = "theText";
        new_image.src = image.src;           
        // insert new image and remove old
        image.parentNode.insertBefore(new_image,image);
        image.parentNode.removeChild(image);
    }

    setTimeout(updateImage, 1000);
}

Po uruchomieniu tej funkcji, jeśli nadal występują problemy, prawdopodobnie jest to problem buforowania, o którym mówią inne odpowiedzi.

BaroqueBobcat
źródło
3

Jedną z odpowiedzi jest hackerskie dodanie parametru pobierania zapytania, jak sugerowano.

Lepszą odpowiedzią jest wyemitowanie kilku dodatkowych opcji w nagłówku HTTP.

Pragma: no-cache
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Cache-Control: no-cache, must-revalidate

Podając datę w przeszłości, przeglądarka nie będzie jej buforować. Cache-Controlzostał dodany w HTTP / 1.1, a znacznik, który należy ponownie zweryfikować, wskazuje, że serwery proxy nigdy nie powinny obsługiwać starego obrazu, nawet w łagodzących okolicznościach, i Pragma: no-cachenie jest tak naprawdę konieczne w obecnych współczesnych przeglądarkach / pamięciach podręcznych, ale może pomóc w niektórych chrupiących, uszkodzonych starych implementacjach.

Edward KMETT
źródło
3
Brzmiało to tak, jakby działało, ale nadal pokazuje ten sam obraz, nawet przy włamaniach. Dodam informacje o nagłówku do pytania.
QueueHammer
Właśnie zauważyłem, że ciągle odświeżasz ten sam tag img. Przeglądarka prawdopodobnie wykrywa, kiedy idziesz ustawić src, że src się nie zmienił, więc nie przeszkadza w odświeżeniu. (Ponieważ ta kontrola odbywa się na poziomie DOM i nie ma nic wspólnego z celem). Co się stanie, jeśli dodasz „?” + numer - na adres URL pobieranego obrazu?
Edward KMETT,
3

Miałem wymaganie: 1) nie mogę dodać żadnego ?var=xxdo obrazu 2) powinien działać między domenami

Naprawdę podoba mi się opcja nr 4 w tej odpowiedzi z jednym, ale:

  • ma problemy z niezawodną pracą z crossdomainem (i wymaga dotknięcia kodu serwera).

Mój szybki i brudny sposób to:

  1. Utwórz ukryty iframe
  2. Załaduj do niego bieżącą stronę (tak, całą stronę)
  3. iframe.contentWindow.location.reload(true);
  4. Zresetuj źródło obrazu do siebie

Oto jest

function RefreshCachedImage() {
    if (window.self !== window.top) return; //prevent recursion
    var $img = $("#MYIMAGE");
    var src = $img.attr("src");
    var iframe = document.createElement("iframe");
    iframe.style.display = "none";
    window.parent.document.body.appendChild(iframe);
    iframe.src = window.location.href;
    setTimeout(function () {
        iframe.contentWindow.location.reload(true);
        setTimeout(function () {
            $img.removeAttr("src").attr("src", src);
        }, 2000);
    }, 2000);
}

Tak, wiem, setTimeout ... Musisz to zmienić na odpowiednie zdarzenia onload.

jazzcat
źródło
3
<img src='someurl.com/someimage.ext' onload='imageRefresh(this, 1000);'>

Następnie poniżej w javascript

<script language='javascript'>
 function imageRefresh(img, timeout) {
    setTimeout(function() {
     var d = new Date;
     var http = img.src;
     if (http.indexOf("&d=") != -1) { http = http.split("&d=")[0]; } 

     img.src = http + '&d=' + d.getTime();
    }, timeout);
  }
</script>

I tak to się dzieje, gdy obraz się ładuje, planuje jego ponowne załadowanie w ciągu 1 sekundy. Używam tego na stronie z kamerami domowymi różnego typu.

J Fields
źródło
2

Skończyło się na tym, że serwer zmapował każde żądanie obrazu w tym katalogu do źródła, które próbowałem zaktualizować. Następnie mój licznik dopisał numer na końcu nazwy, aby DOM mógł zobaczyć go jako nowy obraz i załadować.

Na przykład

http://localhost/image.jpg
//and
http://localhost/image01.jpg

zażąda tego samego kodu generowania obrazu, ale będzie wyglądał jak inne obrazy w przeglądarce.

var newImage = new Image();
newImage.src = "http://localhost/image.jpg";
var count = 0;
function updateImage()
{
    if(newImage.complete) {
        document.getElementById("theText").src = newImage.src;
        newImage = new Image();
        newImage.src = "http://localhost/image/id/image" + count++ + ".jpg";
    }
    setTimeout(updateImage, 1000);
}
QueueHammer
źródło
8
Miałoby to ten sam problem z buforowaniem wielu kopii obrazu jak rozwiązanie do kwerendy (Paolo i niektóre inne) i wymaga zmian na serwerze.
TomG
2

function reloadImage(imageId)
{
   path = '../showImage.php?cache='; //for example
   imageObject = document.getElementById(imageId);
   imageObject.src = path + (new Date()).getTime();
}
<img src='../showImage.php' id='myimage' />

<br/>

<input type='button' onclick="reloadImage('myimage')" />

Mahmoud
źródło
3
wyjaśnij PO, jak i dlaczego to pomaga zamiast wklejać kod
nomistic
Nie sądzę, żeby ../showImage.phpFri May 01 2015 17:34:18 GMT+0200 (Mitteleuropäische Sommerzeit)to była poprawna nazwa pliku ... Przynajmniej tak próbuje się załadować ...
ByteHamster
zmień path='../showImage.php';napath='../showImage.php?';
BOOMik
2
document.getElementById("img-id").src = document.getElementById("img-id").src

ustaw własny src jako src.

Hardik
źródło
1

Spróbuj użyć bezwartościowego zapytania, aby uczynić go unikalnym adresem URL:

function updateImage()
{
    if(newImage.complete) {
        document.getElementById("theText").src = newImage.src;
        newImage = new Image();
        number++;
        newImage.src = "http://localhost/image.jpg?" + new Date();
    }

    setTimeout(updateImage, 1000);
}
Joel
źródło
Dodano WQS do kodu i potwierdzono, że żądanie zostało zaakceptowane i że przeglądarka widzi odpowiedź przychodzącą z adresu + WQS bez odświeżania obrazu.
QueueHammer
1

Poniższy przykład, oparty na kodzie Doina nr 4, znacznie upraszcza ten kod, document.writezamiast wykorzystywać src go iframedo obsługi CORS. Skupia się tylko na usuwaniu pamięci podręcznej przeglądarki, a nie ponownym ładowaniu każdego obrazu na stronie.

Poniżej jest napisane typescripti korzysta z biblioteki angular $ q promise, po prostu fyi, ale powinno być wystarczająco łatwe do przeniesienia na javascript waniliowy. Metoda ma żyć w klasie maszynopisu.

Zwraca obietnicę, która zostanie rozwiązana po zakończeniu ładowania ramki iframe. Nie bardzo przetestowany, ale działa dobrze dla nas.

    mmForceImgReload(src: string): ng.IPromise<void> {
        var deferred = $q.defer<void>();
        var iframe = window.document.createElement("iframe");

        var firstLoad = true;
        var loadCallback = (e) => {
            if (firstLoad) {
                firstLoad = false;
                iframe.contentWindow.location.reload(true);
            } else {
                if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
                deferred.resolve();
            }
        }
        iframe.style.display = "none";
        window.parent.document.body.appendChild(iframe);
        iframe.addEventListener("load", loadCallback, false);
        iframe.addEventListener("error", loadCallback, false);
        var doc = iframe.contentWindow.document;
        doc.open();
        doc.write('<html><head><title></title></head><body><img src="' + src + '"></body></html>');
        doc.close();
        return deferred.promise;
    }
Zwycięzca
źródło
Aby zabezpieczyć się przed błędami XSS, powinieneś użyć, + encodeURI(src) +aby poprawnie uciec srcw iframe.
Tino
1

Poniższy kod jest przydatny do odświeżenia obrazu po kliknięciu przycisku.

function reloadImage(imageId) {
   imgName = 'vishnu.jpg'; //for example
   imageObject = document.getElementById(imageId);
   imageObject.src = imgName;
}

<img src='vishnu.jpg' id='myimage' />

<input type='button' onclick="reloadImage('myimage')" />
Codemaker
źródło
Doceniony. Ponieważ jest to tylko nieznacznie zmodyfikowana kopia kodu @ Mahmouda, ale w przeciwieństwie do tego tutaj NIE jest odświeżanie obrazu
Tino
0

Rozwiązałem ten problem, wysyłając dane z powrotem przez serwlet.

response.setContentType("image/png");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache, must-revalidate");
response.setDateHeader("Expires", 0);

BufferedImage img = ImageIO.read(new File(imageFileName));

ImageIO.write(img, "png", response.getOutputStream());

Następnie ze strony podajesz serwletowi kilka parametrów, aby pobrać poprawny plik obrazu.

<img src="YourServlet?imageFileName=imageNum1">
Tinymothbrain
źródło
0

Oto moje rozwiązanie. To jest bardzo proste. Planowanie ramek może być lepsze.

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">      
        <title>Image Refresh</title>
    </head>

    <body>

    <!-- Get the initial image. -->
    <img id="frame" src="frame.jpg">

    <script>        
        // Use an off-screen image to load the next frame.
        var img = new Image();

        // When it is loaded...
        img.addEventListener("load", function() {

            // Set the on-screen image to the same source. This should be instant because
            // it is already loaded.
            document.getElementById("frame").src = img.src;

            // Schedule loading the next frame.
            setTimeout(function() {
                img.src = "frame.jpg?" + (new Date).getTime();
            }, 1000/15); // 15 FPS (more or less)
        })

        // Start the loading process.
        img.src = "frame.jpg?" + (new Date).getTime();
    </script>
    </body>
</html>
Timmmm
źródło
0

Nie potrzeba new Date().getTime()shenaniganów. Możesz oszukać przeglądarkę, mając niewidoczny obraz-sztuczkę i używając jQuery .load (), a następnie tworząc nowy obraz za każdym razem:

<img src="" id="dummy", style="display:none;" />  <!-- dummy img -->
<div id="pic"></div>

<script type="text/javascript">
  var url = whatever;
  // You can repeat the following as often as you like with the same url
  $("#dummy").load(url);
  var image = new Image();
  image.src = url;
  $("#pic").html("").append(image);
</script>
Bazley
źródło
0

Proste rozwiązanie: dodaj ten nagłówek do odpowiedzi:

Cache-control: no-store

Dlaczego to działa, zostało jasno wyjaśnione na tej wiarygodnej stronie: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control

Wyjaśnia również, dlaczego no-cachenie działa.

Inne odpowiedzi nie działają, ponieważ:

Caching.deletedotyczy nowej pamięci podręcznej, którą możesz utworzyć do pracy offline, patrz: https://web.dev/cache-api-quick-guide/

Fragmenty używające # w adresie URL nie działają, ponieważ # informuje przeglądarkę, aby nie wysyłała żądania do serwera.

Buster-custer z losową częścią dodaną do adresu działa, ale również zapełni pamięć podręczną przeglądarki. W mojej aplikacji chciałem pobierać obraz 5 MB co kilka sekund z kamery internetowej. Całkowite zamrożenie komputera zajmie tylko godzinę. Nadal nie wiem, dlaczego pamięć podręczna przeglądarki nie jest ograniczona do rozsądnego maksimum, ale jest to zdecydowanie wada.

Roland
źródło
0

Poprawiłem skrypt z AlexMA do wyświetlania mojej kamery internetowej na stronie internetowej, która okresowo przesyła nowy obraz o tej samej nazwie. Miałem problemy z tym, że czasami obraz migotał z powodu uszkodzonego obrazu lub niepełnego (w górę) załadowanego obrazu. Aby zapobiec migotaniu, sprawdzam naturalną wysokość obrazu, ponieważ rozmiar obrazu z mojej kamery internetowej nie zmienił się. Tylko jeśli załadowana wysokość obrazu odpowiada pierwotnej wysokości obrazu, pełny obraz zostanie wyświetlony na stronie.

  <h3>Webcam</h3>
  <p align="center">
    <img id="webcam" title="Webcam" onload="updateImage();" src="https://www.your-domain.com/webcam/current.jpg" alt="webcam image" width="900" border="0" />

    <script type="text/javascript" language="JavaScript">

    // off-screen image to preload next image
    var newImage = new Image();
    newImage.src = "https://www.your-domain.com/webcam/current.jpg";

    // remember the image height to prevent showing broken images
    var height = newImage.naturalHeight;

    function updateImage()
    {
        // for sure if the first image was a broken image
        if(newImage.naturalHeight > height)
        {
          height = newImage.naturalHeight;
        }

        // off-screen image loaded and the image was not broken
        if(newImage.complete && newImage.naturalHeight == height) 
        {
          // show the preloaded image on page
          document.getElementById("webcam").src = newImage.src;
        }

        // preload next image with cachebreaker
        newImage.src = "https://www.your-domain.com/webcam/current.jpg?time=" + new Date().getTime();

        // refresh image (set the refresh interval to half of webcam refresh, 
        // in my case the webcam refreshes every 5 seconds)
        setTimeout(updateImage, 2500);
    }

    </script>
</p>
std8590
źródło
-3

Zastosowałem poniższą koncepcję pierwszego wiązania obrazu fałszywym (buforowym) adresem URL, a następnie powiązania go z prawidłowym adresem URL.

imgcover.ImageUrl = ConfigurationManager.AppSettings["profileLargeImgPath"] + "Myapp_CoverPic_" + userid + "Buffer.jpg";

imgcover.ImageUrl = ConfigurationManager.AppSettings["profileLargeImgPath"] + "Myapp_CoverPic_" + userid + ".jpg";

W ten sposób wymuszam odświeżenie przeglądarki przy użyciu prawidłowego adresu URL.

Prashanth Shivasubramani
źródło