Tôi đã thấy rất nhiều biến thể trong câu trả lời cho cách thực hiện điều này, vì vậy tôi nghĩ rằng tôi đã tóm tắt chúng ở đây (cộng thêm phương pháp thứ 4 của phát minh của riêng tôi):
(1) Thêm một tham số truy vấn bộ nhớ cache duy nhất vào URL, chẳng hạn như:
newImage.src = "image.jpg?t=" + new Date().getTime();
Ưu điểm: 100% đáng tin cậy, nhanh chóng & dễ hiểu và thực hiện.
Nhược điểm: Bỏ qua bộ nhớ đệm hoàn toàn, có nghĩa là sự chậm trễ không cần thiết và sử dụng băng thông bất cứ khi nào hình ảnh không thay đổi giữa các chế độ xem. Sẽ có khả năng lấp đầy bộ đệm của trình duyệt (và bất kỳ bộ đệm trung gian nào) với nhiều, nhiều bản sao của cùng một hình ảnh! Ngoài ra, yêu cầu sửa đổi URL hình ảnh.
Khi nào nên sử dụng: Sử dụng khi hình ảnh liên tục thay đổi, chẳng hạn như đối với nguồn cấp dữ liệu webcam trực tiếp. Nếu bạn sử dụng phương pháp này, hãy đảm bảo tự phục vụ hình ảnh bằng Cache-control: no-cache
các tiêu đề HTTP !!! (Thường thì điều này có thể được thiết lập bằng tệp .htaccess). Nếu không, bạn sẽ dần dần lấp đầy bộ nhớ cache với các phiên bản cũ của hình ảnh!
(2) Thêm tham số truy vấn vào URL chỉ thay đổi khi tệp thực hiện, ví dụ:
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(Đó là mã phía máy chủ PHP, nhưng điểm quan trọng ở đây chỉ là chuỗi truy vấn ? M = [file được sửa đổi lần cuối] được gắn vào tên tệp).
Ưu điểm: 100% đáng tin cậy, nhanh chóng & dễ hiểu và thực hiện, và bảo tồn các lợi thế bộ nhớ đệm một cách hoàn hảo.
Nhược điểm: Yêu cầu sửa đổi URL hình ảnh. Ngoài ra, một chút công việc cho máy chủ - nó phải có quyền truy cập vào thời gian sửa đổi tập tin cuối cùng. Ngoài ra, yêu cầu thông tin phía máy chủ, do đó không phù hợp với giải pháp hoàn toàn chỉ dành cho khách hàng để kiểm tra hình ảnh được làm mới.
Khi nào nên sử dụng: Khi bạn muốn lưu trữ hình ảnh, nhưng có thể cần cập nhật chúng ở cuối máy chủ mà không thay đổi tên tệp. VÀ khi bạn có thể dễ dàng đảm bảo rằng chuỗi truy vấn chính xác được thêm vào mọi phiên bản hình ảnh trong HTML của bạn.
(3) Phục vụ hình ảnh của bạn với tiêu đề Cache-control: max-age=0, must-revalidate
và thêm một mã định danh phân đoạn duy nhất của memcache vào URL, chẳng hạn như:
newImage.src = "image.jpg#" + new Date().getTime();
Ý tưởng ở đây là tiêu đề kiểm soát bộ đệm đặt hình ảnh vào bộ đệm của trình duyệt, nhưng ngay lập tức đánh dấu chúng cũ, vì vậy và mỗi khi chúng được hiển thị lại, trình duyệt phải kiểm tra với máy chủ để xem chúng có thay đổi không. Điều này đảm bảo rằng bộ đệm HTTP của trình duyệt luôn trả về bản sao mới nhất của hình ảnh. Tuy nhiên, các trình duyệt thường sẽ sử dụng lại một bản sao trong bộ nhớ của hình ảnh nếu chúng có và thậm chí không kiểm tra bộ đệm HTTP của chúng trong trường hợp đó. Để ngăn chặn điều này, một định danh phân đoạn được sử dụng: So sánh các hình ảnh trong bộ nhớ src
bao gồm định danh phân đoạn, nhưng nó bị tước bỏ trước khi truy vấn bộ đệm HTTP. (Vì vậy, ví dụ, image.jpg#A
và image.jpg#B
cả hai có thể được hiển thị từ image.jpg
mục trong bộ đệm HTTP của trình duyệt, nhưngimage.jpg#B
sẽ không bao giờ được hiển thị bằng dữ liệu hình ảnh được giữ lại trong bộ nhớ từ khi image.jpg#A
được hiển thị lần cuối).
Ưu điểm: Sử dụng đúng các cơ chế bộ đệm HTTP và sử dụng các hình ảnh được lưu trong bộ nhớ cache nếu chúng không thay đổi. Hoạt động cho các máy chủ bị sặc trên chuỗi truy vấn được thêm vào URL hình ảnh tĩnh (vì các máy chủ không bao giờ thấy số nhận dạng phân đoạn - chúng chỉ dành cho sử dụng riêng của trình duyệt).
Nhược điểm: Dựa vào hành vi hơi mơ hồ (hoặc ít nhất là tài liệu kém) của các trình duyệt, liên quan đến hình ảnh có số nhận dạng phân đoạn trong URL của chúng (Tuy nhiên, tôi đã thử nghiệm thành công điều này trong FF27, Chrome33 và IE11). Vẫn không gửi yêu cầu xác nhận lại cho máy chủ cho mọi chế độ xem hình ảnh, điều này có thể quá mức nếu hình ảnh chỉ thay đổi hiếm khi và / hoặc độ trễ là một vấn đề lớn (vì bạn cần chờ phản hồi xác nhận lại ngay cả khi hình ảnh được lưu trong bộ nhớ cache vẫn còn tốt) . Yêu cầu sửa đổi URL hình ảnh.
Khi nào nên sử dụng: Sử dụng khi hình ảnh có thể thay đổi thường xuyên hoặc cần được làm mới liên tục bởi máy khách mà không có sự tham gia của tập lệnh phía máy chủ, nhưng bạn vẫn muốn có lợi thế của bộ nhớ đệm. Ví dụ: bỏ phiếu webcam trực tiếp cập nhật hình ảnh bất thường cứ sau vài phút. Hoặc, sử dụng thay vì (1) hoặc (2) nếu máy chủ của bạn không cho phép truy vấn trên các URL hình ảnh tĩnh.
(4) Buộc làm mới một hình ảnh cụ thể bằng Javascript, bằng cách trước tiên tải nó vào một ẩn <iframe>
và sau đó gọi location.reload(true)
vào iframe contentWindow
.
Các bước là:
Tải hình ảnh để được làm mới vào iframe ẩn. Đây chỉ là một bước thiết lập - nó có thể được thực hiện trước khi làm mới thực tế, nếu muốn. Nó thậm chí không quan trọng nếu hình ảnh không tải ở giai đoạn này!
Khi đã xong, hãy bỏ trống tất cả các bản sao của hình ảnh đó trên (các) trang của bạn hoặc bất cứ nơi nào trong bất kỳ nút DOM nào (ngay cả các trang ngoài trang được lưu trữ trong các biến javascript). Điều này là cần thiết bởi vì trình duyệt có thể hiển thị hình ảnh từ một bản sao trong bộ nhớ cũ (đặc biệt là IE11): Bạn cần đảm bảo tất cả các bản sao trong bộ nhớ đều bị xóa, trước khi làm mới bộ đệm HTTP. Nếu mã javascript khác đang chạy không đồng bộ, bạn cũng có thể cần ngăn mã đó tạo các bản sao mới của hình ảnh được làm mới trong thời gian đó.
Gọi iframe.contentWindow.location.reload(true)
. Các true
lực lượng bỏ qua bộ đệm, tải lại trực tiếp từ máy chủ và ghi đè lên bản sao được lưu trong bộ nhớ cache hiện có.
Một khi nó đã hoàn thành tái -loading, khôi phục lại hình ảnh blanked. Bây giờ họ sẽ hiển thị phiên bản mới từ máy chủ!
Đối với hình ảnh cùng tên miền, bạn có thể tải hình ảnh trực tiếp vào iframe. Đối với hình ảnh tên miền chéo, thay vào đó, bạn phải tải trang HTML từ tên miền có chứa hình ảnh trong <img>
thẻ, nếu không, bạn sẽ gặp lỗi "Truy cập bị từ chối" khi cố gắng gọi iframe.contentWindow.reload(...)
.
Ưu điểm: Hoạt động giống như hàm image.reload () mà bạn muốn DOM có! Cho phép hình ảnh bằng cách lưu trữ thông thường (ngay cả với ngày hết hạn trong tương lai nếu bạn muốn, do đó tránh được xác nhận lại thường xuyên). Cho phép bạn làm mới một hình ảnh cụ thể mà không thay đổi URL cho hình ảnh đó trên trang hiện tại hoặc trên bất kỳ trang nào khác, chỉ sử dụng mã phía máy khách.
Nhược điểm: Dựa vào Javascript. Không được đảm bảo 100% để hoạt động chính xác trong mọi trình duyệt (mặc dù tôi đã thử nghiệm thành công điều này trong FF27, Chrome33 và IE11). Rất phức tạp so với các phương pháp khác.
Khi nào nên sử dụng: Khi bạn có một bộ sưu tập các hình ảnh tĩnh cơ bản mà bạn muốn lưu vào bộ nhớ cache, nhưng bạn vẫn cần có thể cập nhật chúng đôi khi và nhận được phản hồi trực quan ngay lập tức rằng bản cập nhật đã diễn ra. (Đặc biệt là khi chỉ làm mới toàn bộ trang trình duyệt sẽ không hoạt động, như trong một số ứng dụng web được xây dựng trên AJAX chẳng hạn). Và khi các phương thức (1) - (3) không khả thi vì (vì bất kỳ lý do gì), bạn không thể thay đổi tất cả các URL có khả năng hiển thị hình ảnh bạn cần cập nhật. (Lưu ý rằng sử dụng 3 phương thức đó, hình ảnh sẽ được làm mới, nhưng nếu một trang khác sau đó cố gắng hiển thị hình ảnh đó mà không có chuỗi truy vấn hoặc mã định danh phân đoạn thích hợp, thay vào đó, nó có thể hiển thị phiên bản cũ hơn).
Các chi tiết thực hiện điều này theo cách cổ tích mạnh mẽ và linh hoạt được đưa ra dưới đây:
Giả sử trang web của bạn chứa một pixel 1x1 pixel trống ở đường dẫn URL /img/1x1blank.gif
và cũng có tập lệnh PHP một dòng sau (chỉ bắt buộc để áp dụng làm mới bắt buộc cho hình ảnh tên miền chéo và có thể được viết lại bằng bất kỳ ngôn ngữ kịch bản phía máy chủ nào , tất nhiên) tại đường dẫn URL /echoimg.php
:
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
Sau đó, đây là một triển khai thực tế về cách bạn có thể làm tất cả điều này trong Javascript. Trông có vẻ hơi phức tạp, nhưng có rất nhiều ý kiến và chức năng quan trọng chỉ là forceImgReload () - hai hình ảnh đầu tiên chỉ trống và không trống, và nên được thiết kế để hoạt động hiệu quả với HTML của riêng bạn, vì vậy hãy mã hóa chúng thành làm việc tốt nhất cho bạn; nhiều sự phức tạp trong chúng có thể không cần thiết cho trang web của bạn:
// 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);
}
Sau đó, để buộc làm mới hình ảnh nằm trên cùng một tên miền với trang của bạn, bạn chỉ có thể thực hiện:
forceImgReload("myimage.jpg");
Để làm mới hình ảnh từ một nơi khác (tên miền chéo):
forceImgReload("http://someother.server.com/someimage.jpg", true);
Một ứng dụng nâng cao hơn có thể là tải lại hình ảnh sau khi tải phiên bản mới lên máy chủ của bạn, chuẩn bị giai đoạn ban đầu của quá trình tải lại đồng thời với tải lên, để giảm thiểu độ trễ tải lại có thể nhìn thấy cho người dùng. Nếu bạn đang thực hiện tải lên qua AJAX và máy chủ đang trả về một mảng JSON rất đơn giản [thành công, chiều rộng, chiều cao] thì mã của bạn có thể trông giống như thế này:
// 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));
}
Lưu ý cuối cùng: Mặc dù chủ đề này là về hình ảnh, nhưng nó cũng có khả năng áp dụng cho các loại tệp hoặc tài nguyên khác. Ví dụ: ngăn chặn việc sử dụng tập tin cũ hoặc tập tin css hoặc thậm chí có thể làm mới các tài liệu PDF đã cập nhật (chỉ sử dụng (4) nếu được thiết lập để mở trong trình duyệt). Phương thức (4) có thể yêu cầu một số thay đổi đối với javascript ở trên, trong những trường hợp này.