我正在访问我的网站上的链接,每次访问时都会提供一个新图像.
我遇到的问题是,如果我尝试在后台加载图像然后更新页面上的图像,图像不会改变 - 虽然它在我重新加载页面时更新.
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); }
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
我需要在页面上强制刷新该图像.有任何想法吗?
尝试在网址末尾添加缓存破解程序:
newImage.src = "http://localhost/image.jpg?" + new Date().getTime();
这将在您创建图像时自动附加当前时间戳,并且它将使浏览器再次查看图像,而不是检索缓存中的图像.
我已经看到了如何做到这一点的答案有很多变化,所以我想我在这里总结一下(加上我自己发明的第四种方法):
newImage.src = "image.jpg?t=" + new Date().getTime();
优点: 100%可靠,快速,易于理解和实施.
缺点:完全禁用缓存,这意味着只要图像在视图之间不发生变化,就会出现不必要的延迟和带宽使用.可能会为浏览器缓存(以及任何中间缓存)填充许多完全相同图像的副本!此外,还需要修改图像URL.
何时使用:在图像不断变化时使用,例如用于实时网络摄像头.如果您使用此方法,请确保使用Cache-control: no-cache
HTTP标头自己提供图像! (通常可以使用.htaccess文件设置).否则,您将逐步使用旧版本的图像填充缓存!
echo '';
(这是PHP服务器端代码,但重要的一点是,文件名附加了一个?m = [文件最后修改时间]查询字符串).
优点: 100%可靠,快速,易于理解和实施,并完美保留缓存优势.
缺点:需要修改图像URL.此外,还需要为服务器做更多工作 - 它必须能够访问文件最后修改时间.此外,还需要服务器端信息,因此不适合纯粹的客户端解决方案来检查刷新的图像.
何时使用:当您想要缓存图像时,可能需要在服务器端不时更新它们而不更改文件名本身.当您可以轻松确保将正确的查询字符串添加到HTML中的每个图像实例时.
Cache-control: max-age=0, must-revalidate
,并为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
永远不会使用image.jpg#A
上次显示的内存保留图像数据显示).
优点:正确使用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.
When to use: Use when images may change frequently, or need to be refreshed intermittently by the client without server-side script involvement, but where you still want the advantage of caching. For example, polling a live webcam that updates an image irregularly every few minutes. Alternatively, use instead of (1) or (2) if your server doesn't allow querystrings on static image URLs.
and then calling location.reload(true)
on the iframe's contentWindow
.The steps are:
Load the image to be refreshed into a hidden iframe. 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!
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.
完成重新加载后,恢复已消隐的图像.他们现在应该从服务器显示新版本!
对于同域图像,您可以直接将图像加载到iframe中.对于跨域图像,您必须从域中加载包含标记中图像的HTML页面,否则在尝试调用时会出现"拒绝访问"错误
iframe.contentWindow.reload(...)
.
Pros: Works just like the image.reload() function you wish the DOM had! Allows images to by cached normally (even with in-the-future expiry dates if you want them, thus avoiding frequent revalidation). Allows you to refresh a particular image without altering the URLs for that image on the current page, or on any other pages, using only client-side code.
Cons: Relies on Javascript. Not 100% guaranteed to work properly in every browser (I've tested this successfully in FF27, Chrome33, and IE11 though). Very complicated relative to the other methods.
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).
The details of implementing this in a fairy robust and flexible manner are given below:
Let's assume your website contains a blank 1x1 pixel .gif at the URL path /img/1x1blank.gif
, and also has the following one-line PHP script (only required for applying forced refresh to cross-domain images, and can be rewritten in any server-side scripting language, of course) at the URL path /echoimg.php
:
Then, here's a realistic implementation of how you might do all this in Javascript. It looks a bit complicated, but there's a lot of comments, and the important function is just forceImgReload() - the first two just blank and un-blank images, and should be designed to work efficiently with your own HTML, so code them as works best for you; much of the complications in them may be unnecessary for your website:
// 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 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); }
Then, to force a refresh of an image located on the same domain as your page, you can just do:
forceImgReload("myimage.jpg");
To refresh an image from somewhere else (cross-domain):
forceImgReload("http://someother.server.com/someimage.jpg", true);
A more advanced application might be to reload an image after uploading a new version to your server, preparing the initial stage of the reload process simultaneous with the upload, to minimize the visible reload delay to the user. If you're doing the upload via AJAX, and the server is returning a very simple JSON array [success, width, height] then your code might look something like this:
// fileForm is a reference to the form that has a the 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)); }
A final note: Although this topic is about images, it potentially applies to other kinds of files or resources also. For example, preventing the use of stale script or css files, or perhaps even refreshing updated PDF documents (using (4) only if set up to open in-browser). Method (4) might require some changes to the above javascript, in these cases.
作为......的替代方案
newImage.src = "http://localhost/image.jpg?" + new Date().getTime();
...看起来...
newImage.src = "http://localhost/image.jpg#" + new Date().getTime();
...假设您返回了正确的Cache-Control
标头,足以欺骗浏览器缓存而不绕过任何上游缓存.虽然你可以用......
Cache-Control: no-cache, must-revalidate
...你失去了If-Modified-Since
或者If-None-Match
标题的好处,所以像......
Cache-Control: max-age=0, must-revalidate
...如果实际上没有更改,则应阻止浏览器重新下载整个图像.测试并使用IE,Firefox和Chrome.令人恼火的是它在Safari上失败,除非你使用...
Cache-Control: no-store
...虽然这仍然可能比用数百个相同的图像填充上游缓存更可取,特别是当它们在您自己的服务器上运行时.;-)
更新(2014-09-28):现在看来Cache-Control: no-store
Chrome也需要它.
创建新图像后,您是从DOM中删除旧图像并将其替换为新图像吗?
您可以在每次updateImage调用时抓取新图像,但不将它们添加到页面中.
有很多方法可以做到这一点.像这样的东西会起作用.
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); }
在完成这项工作之后,如果仍然存在问题,则可能是其他答案所讨论的缓存问题.