用相同的URL刷新一个新的图像
我正在访问我的网站上的链接,每次访问时都会提供一个新的图像。
我遇到的问题是,如果我尝试在后台加载图像,然后更新页面上的图像,则图像不会更改 – 尽pipe在重新加载页面时会更新图像。
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
我需要强制刷新页面上的那张图片。 有任何想法吗?
尝试在url的末尾添加缓冲区破解程序:
newImage.src = "http://localhost/image.jpg?" + new Date().getTime();
这将在创build图像时自动添加当前时间戳,并且会使浏览器再次查找图像,而不是检索caching中的图像。
作为替代…
newImage.src = "http://localhost/image.jpg?" + new Date().getTime();
…看起来…
newImage.src = "http://localhost/image.jpg#" + new Date().getTime();
…足以在不绕过任何上游caching的情况下欺骗浏览器caching,假设您返回了正确的Cache-Control
标头。 虽然你可以使用…
Cache-Control: no-cache, must-revalidate
…你失去了If-Modified-Since
或者If-None-Match
头文件的好处,所以像…
Cache-Control: max-age=0, must-revalidate
…应该防止浏览器重新下载整个图像,如果它没有真正改变。 testing并在IE,Firefox和Chrome上工作。 烦人的是它在Safari上失败,除非你使用…
Cache-Control: no-store
…虽然这仍然可能比填充上游caching数百个相同的图像更好,特别是当他们在自己的服务器上运行。 😉
更新 (2014-09-28):现在它看起来像Cache-Control: no-store
Chrome也需要Cache-Control: no-store
。
我已经看到了很多关于如何做到这一点的答案,所以我想我会在这里总结一下(再加上我自己发明的第四种方法):
(1)向URL添加唯一的caching查询参数,例如:
newImage.src = "image.jpg?t=" + new Date().getTime();
优点: 100%可靠,快速,易于理解和实施。
缺点:完全忽略高速caching,意味着每当图像在视图之间不发生变化时就会使用不必要的延迟和带宽。 将可能填充浏览器caching(和任何中间caching)与许多完全相同的图像副本! 另外,还需要修改图片的url。
何时使用:在图像不断变化时使用,例如用于实况networking摄像头馈送。 如果您使用这种方法,请确保使用Cache-control: no-cache
服务图像本身Cache-control: no-cache
HTTP标头! (通常这可以使用.htaccess文件来设置)。 否则,你将逐渐用旧版本的图像填充caching!
(2)将查询参数添加到仅在文件所做更改时才更改的URL,例如:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(这是PHP服务器端的代码,但重要的一点是这只是一个?m = [文件最后修改时间]查询string追加到文件名)。
优点: 100%可靠,快速,易于理解和实施, 并完美保留caching优势。
缺点:需要修改图片url。 另外,为服务器多做一点工作 – 必须访问文件最后修改的时间。 而且,需要服务器端的信息,所以不适合纯粹的客户端scheme来检查刷新的图像。
何时使用:如果要caching图像,但可能需要在服务器端不时更新它们,而不更改文件名本身。 而且当你可以很容易地确保正确的查询string被添加到HTML中的每个图像实例。
(3)使用头部Cache-control: max-age=0, must-revalidate
,并为URL添加一个唯一的memcache- burning分片标识符,例如:
newImage.src = "image.jpg#" + new Date().getTime();
这里的想法是,caching控制标题将图像放入浏览器caching中,但立即将其标记为已过期,以便每当浏览器重新显示时,浏览器都必须检查服务器是否已更改。 这确保浏览器的HTTPcaching始终返回图像的最新副本。 但是,浏览器通常会重新使用映像的内存副本(如果有的话),在这种情况下甚至不检查其HTTPcaching。 为了防止这种情况,使用片段标识符:内存中的图像src
的比较包括片段标识符,但是在查询HTTPcaching之前将其删除。 (因此,例如, image.jpg#A
和image.jpg#B
都可以从浏览器的HTTPcaching中的image.jpg
条目中显示,但image.jpg#B
永远不会使用内存中保留的图像数据显示当image.jpg#A
被最后显示时)。
优点:正确使用HTTPcaching机制,并使用caching的图像,如果他们没有改变。 适用于添加到静态图像URL的查询string的服务器(因为服务器永远不会看到片段标识符 – 它们仅供浏览器自己使用)。
缺点:依赖于浏览器的有些可疑(或者至less文档不完整)的行为,对于URL中带有片段标识符的图像(但是,我已经在FF27,Chrome33和IE11中成功testing过了)。 每个图像视图仍然会向服务器发送一个重新validation请求,如果图像只是变化很less,并且/或者延迟是一个大问题,那么这可能是矫枉过正的(因为即使caching图像仍然很好,您仍然需要等待重新validation响应) 。 需要修改图片url。
何时使用:当图像可能频繁变化时使用,或需要由客户端间歇性地刷新,而不涉及服务器端脚本,但是仍然需要caching的优势。 例如,轮询一个现场摄像头,每隔几分钟就会不定期更新图像。 或者,如果您的服务器不允许在静态图像URL上查询string,请使用(1)或(2)来代替。
(4)使用Javascript强制刷新特定图像,首先将其加载到隐藏的<iframe>
,然后在iframe的contentWindow
上调用location.reload(true)
。
步骤是:
-
将要刷新的图像加载到隐藏的iframe中。 这只是一个设置步骤 – 如果需要,可以提前做很长时间的实际刷新。 在这个阶段,如果图片加载失败,那也不算什么!
-
一旦完成,在页面上或者任何DOM节点的任何地方(甚至是存储在javascriptvariables中的离页页面)的空白处,都可以清空该图像的所有副本。 这是必要的,因为浏览器可能会显示来自旧内存副本的图像(尤其是IE11):在刷新HTTPcaching之前,您需要确保清除所有内存中的副本。 如果其他JavaScript代码asynchronous运行,则可能还需要防止该代码同时创build要刷新的图像的新副本。
-
调用
iframe.contentWindow.location.reload(true)
。true
强制caching绕过,直接从服务器重新加载并覆盖现有的caching副本。 -
一旦完成重新加载,恢复空白的图像。 他们现在应该显示来自服务器的新版本!
对于同域图像,您可以直接将图像加载到iframe中。 对于跨域图片,您必须从您的域中加载一个HTML页面,该页面包含<img>
标签中的<img>
,否则在尝试调用iframe.contentWindow.reload(...)
时将会出现“访问被拒绝”错误iframe.contentWindow.reload(...)
。
优点:就像你想要 DOM的image.reload()函数一样工作! 通过正常caching允许图像(即使在未来的到期date,如果你想要它们,从而避免频繁的重新validation)。 允许您刷新特定图像,而不必在当前页面或任何其他页面上使用客户端代码更改该图像的URL。
缺点:依赖于Javascript。 不是100%保证在每个浏览器中正常工作(我已经在FF27,Chrome33和IE11中成功testing过)。 相对于其他方法来说非常复杂。
何时使用:当您想要caching基本静态图像的集合时,但您仍然需要能够偶尔更新它们,并立即获得更新发生的视觉反馈。 (尤其是当只刷新整个浏览器页面将无法正常工作,例如在一些基于AJAX构build的Web应用程序中)。 而当方法(1) – (3)不可行,因为(无论什么原因),你不能改变所有可能显示你需要更新的图像的URL。 (请注意,使用这3种方法会刷新图像,但是如果另一个页面尝试显示没有适当的查询string或片段标识符的图像,则可能会显示旧版本)。
下面给出了以神仙健壮和灵活的方式实施这个细节:
假设您的网站在URLpath/img/1x1blank.gif
包含一个空白的1×1像素.gif,并且还具有以下一行PHP脚本(仅用于应用强制刷新跨域图像,并且可以重写任何服务器端脚本语言,当然)在URLpath/echoimg.php
:
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
然后,这里是一个现实的实现,你可能会做这一切在Javascript中。 它看起来有些复杂,但有很多评论,重要的function只是forceImgReload() – 前两个只是空白和非空白的图像,应该被devise成与你自己的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 - ie prepend protocol, server/domain, and path if not present in src */, imgs, img, i; for each (/* window accessible from this one, ie 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, eg: 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 (ie 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)); }
最后一点:虽然这个主题是关于图像的,但也可能适用于其他types的文件或资源。 例如,防止使用陈旧的脚本或css文件,或者甚至刷新更新的PDF文档(仅当设置为在浏览器中打开时才使用(4))。 在这些情况下,方法(4)可能需要对上述JavaScript进行一些更改。
一个答案是hackishly添加一些获取查询参数就像已经build议。
更好的答案是在HTTP标头中发出一些额外的选项。
Pragma: no-cache Expires: Fri, 30 Oct 1998 14:19:41 GMT Cache-Control: no-cache, must-revalidate
通过提供过去的date,它不会被浏览器caching。 在HTTP / 1.1中添加了Cache-Control
,而must-revalidate标签则表明代理服务器不应该在情有可原的情况下提供旧的映像,而Pragma: no-cache
对于当前的浏览器/caching来说并不是必须的,帮助一些破旧的旧实现。
创build新的图像后,你是从DOM中删除旧的图像,并将其replace为新的?
每次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); }
得到这个工作后,如果还有问题,可能是像其他答案一样的caching问题。
我最终做的是让服务器映射在该目录的图像的任何请求到我正在尝试更新的源。 然后,我的计时器在名称的末尾附加了一个数字,以便DOM将它看作一个新的图像并加载它。
例如
http://localhost/image.jpg //and http://localhost/image01.jpg
将请求相同的图像生成代码,但它会看起来像浏览器不同的图像。
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); }
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')" />
尝试使用无价值的查询string,使其成为一个独特的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); }
document.getElementById("img-id").src = document.getElementById("img-id").src
设置自己的src作为其src。
我有一个要求:1)不能添加任何?var=xx
的图像2)它应该跨域的工作
我真的很喜欢这个答案中的#4选项,但是:
- 它在跨域可靠地工作时遇到问题(并且需要触摸服务器代码)。
我的快速和肮脏的方式是:
- 创build隐藏的iframe
- 加载当前页面 (是整个页面)
-
iframe.contentWindow.location.reload(true);
- 重新设置图像源自己
这里是
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); }
是的,我知道,setTimeout …你必须改变适当的onload事件。
我通过一个servlet发回数据来解决这个问题。
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());
然后从页面中给你一些参数的servlet抓取正确的图像文件。
<img src="YourServlet?imageFileName=imageNum1">
这是我的解决scheme。 这很简单。 框架调度可能会更好。
<!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>
基于Doin的#4代码,下面的例子使用document.write
而不是iframe
的src
来简化代码,以支持CORS。 也只关注于破坏浏览器caching,而不是重新加载页面上的每个图像。
Below is written in typescript
and uses the angular
$q promise library, just fyi, but should be easy enough to port to vanilla javascript. Method is meant to live inside a typescript class.
Returns a promise that will be resolved when the iframe has completed reloading. Not heavily tested, but works well for us.
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; }
I used the below concept of first binding the image with a false(buffer) url and next binding it with the valid url.
imgcover.ImageUrl = ConfigurationManager.AppSettings["profileLargeImgPath"] + "Myapp_CoverPic_" + userid + "Buffer.jpg"; imgcover.ImageUrl = ConfigurationManager.AppSettings["profileLargeImgPath"] + "Myapp_CoverPic_" + userid + ".jpg";
This way, I am forcing the browser to refresh with valid url.
<img src='someurl.com/someimage.ext' onload='imageRefresh(this, 1000);'>
Then below in some 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>
And so what this does is, when the image loads, schedules it to be reloaded in 1 second. I'm using this on a page with home security cameras of varying type.