关于如何做到这一点,我已经看到了很多不同的答案,所以我想在这里总结一下(加上我自己发明的第4种方法):
(1)在URL中添加一个唯一的缓存破坏查询参数,例如:
newImage.src = "image.jpg?t=" + new Date().getTime();
优点:100%可靠,快速,易于理解和实现。
缺点:完全绕过缓存,这意味着当图像在视图之间没有改变时,不必要的延迟和带宽使用。将潜在地填满浏览器缓存(和任何中间缓存)与许多,许多完全相同的图像的副本!另外,需要修改图像URL。
何时使用:当图像不断变化时使用,例如用于实时网络摄像头馈送。如果你使用这种方法,请确保使用缓存控制服务图像本身:无缓存HTTP头!!(通常可以使用.htaccess文件进行设置)。否则,您将逐步用旧版本的图像填充缓存!
(2)在URL中添加查询参数,该参数仅在文件更改时才更改,例如:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(这是PHP服务器端代码,但这里的重点是一个?m=[文件最后修改时间]查询字符串被附加到文件名)。
优点:100%可靠,快速,易于理解和实现,并完美地保留缓存优势。
缺点:需要修改图像URL。此外,服务器还需要做一些工作——它必须访问文件最后修改的时间。此外,它需要服务器端信息,因此不适合纯客户端解决方案来检查刷新映像。
何时使用:当你想缓存图像,但可能需要在服务器端不时更新它们,而不改变文件名本身。AND时,您可以轻松地确保将正确的查询字符串添加到HTML中的每个图像实例中。
(3)使用Cache-control: max-age=0,必须重新验证,并在URL中添加一个唯一的memcache-busting片段标识符,例如:
newImage.src = "image.jpg#" + new Date().getTime();
The idea here is that the cache-control header puts images in the browser cache, but immediately markes them stale, so that and every time they are re-displayed the browser must check with the server to see if they've changed. This ensures that the browser's HTTP cache always returns the latest copy of the image. However, browsers will often re-use an in-memory copy of an image if they have one, and not even check their HTTP cache in that case. To prevent this, a fragment identifier is used: Comparison of in-memory image src's includes the fragment identifier, but it gets stripped of before querying the HTTP cache. (So, e.g., image.jpg#A and image.jpg#B might both be displayed from the image.jpg entry in the browser's HTTP cache, but image.jpg#B would never be displayed using in-memory retained image data from when image.jpg#A was last displayed).
优点:正确使用HTTP缓存机制,并使用缓存的图像,如果他们没有改变。适用于被添加到静态图像URL的查询字符串阻塞的服务器(因为服务器永远不会看到片段标识符-它们只供浏览器自己使用)。
Cons: Relies on somewhat dubious (or at least poorly documented) behaviour of browsers, in regard to images with fragment identifiers in their URLs (However, I've tested this successfully in FF27, Chrome33, and IE11). Does still send a revalidation request to the server for every image view, which may be overkill if images only change rarely and/or latency is a big issue (since you need to wait for the revalidation response even when the cached image is still good). Requires modifying image URLs.
何时使用:当图像可能频繁更改,或者需要由客户端间歇刷新而不涉及服务器端脚本,但仍然需要缓存优势时使用。例如,轮询每隔几分钟就会不定期更新图像的实时网络摄像头。或者,如果你的服务器不允许静态图像url上的查询字符串,使用(1)或(2)代替。
[EDIT 2021:不再适用于最近的Chrome和Edge:这些浏览器中的内部memcache现在忽略片段标识符(可能是因为切换到Blink引擎?)但是请参见下面的方法(4),现在在这两种浏览器上要容易得多,所以考虑将这种方法与(4)的简化版本结合起来,以覆盖这两种浏览器]。
(4)使用Javascript强制刷新一个特定的图像,首先将它加载到一个隐藏的<iframe>,然后在iframe的contentWindow上调用location.reload(true)。
步骤如下:
Load the image to be refreshed into a hidden iframe. [EDIT 2021: For Chrome and Edge, load a HTML page with an <img> tag, not the raw image file]. This is just a setup step - it can be done long in advance the actual refresh, if desired. It doesn't even matter if the image fails to load at this stage!
[EDIT 2021: This step is now unnecessary in recent Chrome and Edge]. Once that's done, blank out all copies of that image on your page(s) or anywhere in any DOM nodes (even off-page ones stored in javascript variables). This is necessary because the browser may otherwise display the image from a stale in-memory copy (IE11 especially does this): You need to ensure all in-memory copies are cleared, before refreshing the HTTP cache. If other javascript code is running asynchronously, you may also need to prevent that code from creating new copies of the to-be-refreshed image in the meantime.
Call iframe.contentWindow.location.reload(true). The true forces a cache bypass, reloading directly from the server and overwriting the existing cached copy.
[EDIT 2021: This step is now unnecessary in recent Chrome and Edge - on those browsers, existing images will just automatically update themselves after the previous step!] Once it's finished re-loading, restore the blanked images. They should now display the fresh version from the server!
对于同域图像,可以直接将图像加载到iframe中。[编辑2021年:不Chrome, Edge]。对于跨域图像,你必须从你的域加载一个包含<img>标记图像的HTML页面,否则当你试图调用iframe.contentWindow.reload(…)时,你会得到一个“Access Denied”错误。[为Chrome和Edge也这样做]。
优点:就像你希望DOM拥有的image.reload()函数一样!允许正常缓存图像(如果你想要它们,甚至在未来的过期日期,从而避免频繁的重新验证)。允许您仅使用客户端代码刷新特定图像,而无需更改当前页面或任何其他页面上该图像的url。
缺点:依赖Javascript。不能100%保证在每个浏览器都能正常工作(我已经在FF27、Chrome33和IE11上测试成功了)。相对于其他方法非常复杂。[编辑2021:除非你只需要最近的Chrome和Edge支持,在这种情况下,事情就简单多了]。
When to use: When you have a collection of basically static images that you'd like cached, but you still need to be able to update them occasionally and get immediate visual feedback that the update took place. (Especially when just refreshing the whole browser page wouldn't work, as in some web apps built on AJAX for example). And when methods (1)-(3) aren't feasible because (for whatever reason) you can't change all the URLs that might potentially display the image you need to have updated. (Note that using those 3 methods the image will be refreshed, but if another page then tries to displays that image without the appropriate querystring or fragment identifier, it may show an older version instead).
以一种非常健壮和灵活的方式实现这一点的细节如下:
让我们假设您的网站在URL路径/img/1x1blank.gif中包含一个空白的1x1像素。gif,并且在URL路径/echoimg.php中也有以下一行PHP脚本(只需要应用强制刷新跨域图像,当然可以用任何服务器端脚本语言重写):
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
然后,这里是如何在Javascript中实现所有这些的现实实现。它看起来有点复杂,但有很多注释,重要的函数只是forceImgReload() -前两个只是空白和非空白的图像,应该设计为与您自己的HTML有效地工作,所以编码它们最适合您;它们中的许多复杂之处对你的网站来说可能是不必要的:
// 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);
}
然后,要强制刷新与页面位于同一域的图像,您可以这样做:
forceImgReload("myimage.jpg");
从其他地方(跨域)刷新一个图像:
forceImgReload("http://someother.server.com/someimage.jpg", true);
更高级的应用程序可能是在将新版本上载到服务器后重新加载图像,在上传的同时准备重新加载过程的初始阶段,以尽量减少用户可见的重新加载延迟。如果你通过AJAX上传,服务器返回一个非常简单的JSON数组[success, width, height],那么你的代码可能是这样的:
// 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));
}
最后注意:虽然这个主题是关于图像的,但它也可能适用于其他类型的文件或资源。例如,防止使用过时的脚本或css文件,或者甚至刷新更新后的PDF文档(仅在设置为在浏览器中打开时使用(4))。在这些情况下,方法(4)可能需要对上面的javascript进行一些更改。