我已经看到了很多有关如何执行此操作的答案的变体,所以我想我在这里进行了总结(加上我自己发明的第4种方法):
(1)在网址中添加唯一的缓存清除查询参数,例如:
newImage.src = "image.jpg?t=" + new Date().getTime();
优点: 100%可靠,快速且易于理解和实施。
缺点:完全绕过缓存,这意味着只要图像不存在,就不必要的延迟和带宽使用在视图之间改变。可能会用许多很多完全相同的图像副本填充浏览器缓存(和任何中间缓存)!另外,需要修改图像URL。
使用时间:当图像不断变化时使用,例如用于实时网络摄像头。如果使用此方法,请确保为图像本身提供Cache-control: no-cache
HTTP标头!!! (通常可以使用.htaccess文件进行设置)。否则,您将逐步用旧版本的图像填充缓存!
(2)将查询参数添加到仅在文件更改时才更改的URL,例如:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(这是PHP服务器端代码,但这里的重点只是一个?m = [文件上次修改时间]在文件名后附加查询字符串)。
优点: 100%可靠,快速且易于理解和实施,并且完美保留了缓存优势。
缺点:需要修改图像URL。此外,服务器还需要做更多的工作-它必须有权访问文件的最后修改时间。此外,还需要服务器端信息,因此不适合用于仅客户端解决方案来检查刷新的图像。
何时使用:当您要缓存图像时,但可能需要在服务器端不时更新它们而不更改文件名本身。并且可以轻松地确保将正确的查询字符串添加到HTML中的每个图像实例。
(3)使用header为您的图像提供服务Cache-control: max-age=0, must-revalidate
,并向URL 添加唯一的memcache -busting片段标识符,例如:
newImage.src = "image.jpg#" + new Date().getTime();
这里的想法是,缓存控制标头将图像放入浏览器缓存中,但立即将它们标记为陈旧,因此,每次重新显示它们时,浏览器都必须与服务器核对以查看它们是否已更改。这样可以确保浏览器的HTTP缓存始终返回图像的最新副本。但是,浏览器通常会重复使用映像的内存副本(如果有的话),在这种情况下甚至不会检查其HTTP缓存。为了防止这种情况,使用了片段标识符:内存映像比较src
包括片段标识符,但是在查询HTTP缓存之前将其删除。(因此,例如image.jpg#A
和image.jpg#B
都可能会从image.jpg
浏览器的HTTP缓存中的条目中显示,但image.jpg#B
永远不会使用image.jpg#A
上次显示时的内存中保留图像数据来显示)。
优点:正确使用HTTP缓存机制,并使用缓存的图像(如果未更改)。适用于阻塞添加到静态图片URL的查询字符串的服务器(因为服务器从不看到片段标识符-它们仅供浏览器自己使用)。
缺点:对于URL中带有片段标识符的图像,依赖于浏览器的某种可疑行为(或至少记录不佳的行为)(但是,我已经在FF27,Chrome33和IE11中成功测试了此行为)。仍然会针对每个图像视图向服务器发送重新验证请求,如果仅很少更改图像和/或等待时间是个大问题,这可能会显得过高(因为即使在缓存的图像仍然良好的情况下,您也需要等待重新验证响应) 。需要修改图像URL。
什么时候使用:当图像可能经常更改或需要由客户端间歇刷新而不使用服务器端脚本的情况下使用,但是仍然需要缓存的优势。例如,轮询一个实时摄像头,该摄像头每隔几分钟会不定期地更新图像。或者,如果您的服务器不允许静态图像URL上的查询字符串,请使用(1)或(2)代替。
(4)使用Javascript强制刷新特定的图像,方法是先将其加载到隐藏的图像中<iframe>
,然后再调用location.reload(true)
iframe的contentWindow
。
这些步骤是:
将要刷新的图像加载到隐藏的iframe中。这只是一个设置步骤-如果需要,可以在实际刷新之前很长时间完成。图像在此阶段加载失败甚至都没有关系!
完成此操作后,请清空该页面上或任何DOM节点中任何位置(甚至是存储在javascript变量中的页面外副本)上该图片的所有副本。这是必需的,因为浏览器可能会从过时的内存副本中显示图像(IE11尤其如此):在刷新HTTP缓存之前,您需要确保清除了所有内存副本。如果其他javascript代码正在异步运行,那么您可能还需要同时阻止该代码创建要刷新图像的新副本。
致电iframe.contentWindow.location.reload(true)
。该true
部队缓存旁路,直接从服务器重装并覆盖现有的缓存副本。
重新加载完成后,还原空白图像。他们现在应该显示服务器上的最新版本!
对于相同域的图像,您可以将图像直接加载到iframe中。对于跨域图像,您必须从域中加载一个HTML页面,该页面中包含<img>
标记中的图像,否则,在尝试调用时会出现“访问被拒绝”错误iframe.contentWindow.reload(...)
。
优点:就像您希望 DOM具有的image.reload()函数一样工作!允许按正常方式缓存图像(如果需要的话,即使有到期日期,也可以这样,从而避免了频繁的重新验证)。允许您仅使用客户端代码刷新特定图像,而无需更改当前页面或任何其他页面上该图像的URL。
缺点:依靠Javascript。并非100%保证在每种浏览器中都能正常工作(尽管我已经在FF27,Chrome33和IE11中成功测试了此功能)。相对于其他方法而言非常复杂。
何时使用:当您要缓存一些基本静态的图像时,您仍然需要偶尔进行更新,并获得更新发生后的即时视觉反馈。(尤其是在仅刷新整个浏览器页面时,如在基于AJAX的某些Web应用程序中那样无法工作)。当方法(1)-(3)不可行时,因为(由于某种原因)您不能更改所有可能显示需要更新的图像的URL。(请注意,使用这三种方法将刷新图像,但是如果使用其他方法页面尝试显示该图像而没有适当的查询字符串或片段标识符,则可能会显示旧版本)。
下面给出了以一种强大而灵活的方式实现此操作的详细信息:
假设您的网站在URL路径中包含一个空白的1x1像素.gif /img/1x1blank.gif
,并且还具有以下单行PHP脚本(仅对跨域图像应用强制刷新才需要,并且可以用任何服务器端脚本语言进行重写,当然)在URL路径/echoimg.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数组[成功,宽度,高度],那么您的代码可能看起来像这样:
// 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进行一些更改。