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