これを行う方法についての答えには多くのバリエーションがあるので、ここでそれらを要約すると思いました(さらに、私自身の発明の4番目の方法を追加します):
(1) 次のような一意のキャッシュ無効化クエリ パラメータを URL に追加します。
newImage.src = "image.jpg?t=" + new Date().getTime();
長所: 100% 信頼性が高く、迅速かつ簡単に理解して実装できます。
短所:キャッシュを完全にバイパスします。つまり、ビュー間で画像が変化しない場合は常に、不要な遅延と帯域幅の使用が発生します。ブラウザーのキャッシュ (および中間キャッシュ) が、まったく同じ画像の非常に多くのコピーでいっぱいになる可能性があります。また、画像の URL を変更する必要があります。
いつ使用するか:ライブ Web カメラ フィードなど、画像が絶えず変化する場合に使用します。この方法を使用する場合は、必ずCache-control: no-cache
HTTP ヘッダーを使用して画像自体を提供してください!!! (多くの場合、これは .htaccess ファイルを使用して設定できます)。そうしないと、キャッシュが古いバージョンのイメージで徐々にいっぱいになってしまいます。
(2) ファイルが変更された場合にのみ変更されるクエリ パラメータを URL に追加します。
echo '<img src="image.jpg?m=' . filemtime('image.jpg') . '">';
(これは PHP サーバー側のコードですが、ここで重要なのは、ファイル名に ?m= [file last-modified time]クエリ文字列が追加されていることです)。
長所: 100% の信頼性があり、すばやく簡単に理解して実装でき、キャッシュの利点を完全に維持します。
短所:画像の URL を変更する必要があります。また、サーバーにはもう少し作業が必要です。ファイルの最終変更時刻にアクセスする必要があります。また、サーバー側の情報が必要なため、更新されたイメージを確認する純粋にクライアント側のみのソリューションには適していません。
いつ使用するか:画像をキャッシュしたいが、ファイル名自体を変更せずに時々サーバー側で画像を更新する必要がある場合。そして、HTML 内のすべての画像インスタンスに正しいクエリ文字列が追加されていることを簡単に確認できる場合。
(3) ヘッダー付きの画像を提供し、次Cache-control: max-age=0, must-revalidate
のような一意のmemcacheバスティング フラグメント識別子を URL に追加します。
newImage.src = "image.jpg#" + new Date().getTime();
ここでの考え方は、cache-control ヘッダーが画像をブラウザーのキャッシュに入れますが、すぐに古くなったとマークするため、再表示されるたびにブラウザーはサーバーにチェックを入れて、変更されているかどうかを確認する必要があります。これにより、ブラウザのHTTP キャッシュが常に画像の最新のコピーを返すことが保証されます。ただし、ブラウザーは、画像のメモリ内コピーがある場合はそれを再利用することが多く、その場合、HTTP キャッシュをチェックすることさえありません。これを防ぐために、フラグメント識別子が使用されます。メモリ内のイメージsrc
の比較にはフラグメント識別子が含まれますが、HTTP キャッシュを照会する前に削除されます。(したがって、たとえば、ブラウザの HTTP キャッシュのエントリから両方が表示される可能性がありますimage.jpg#A
が、image.jpg#B
image.jpg
image.jpg#B
が最後に表示されたときからメモリ内に保持された画像データを使用して表示されることはありませんimage.jpg#A
)。
長所: HTTP キャッシュ メカニズムを適切に使用し、変更されていない場合はキャッシュされた画像を使用します。静的な画像の URL に追加されたクエリ文字列でチョークするサーバーに対して機能します (サーバーはフラグメント識別子を認識しないため、ブラウザー自身の使用のみを目的としています)。
短所: URL にフラグメント識別子を含む画像に関して、ブラウザーのやや疑わしい (または少なくとも十分に文書化されていない) 動作に依存しています (ただし、これは FF27、Chrome33、および IE11 で正常にテストされています)。画像ビューごとに再検証リクエストをサーバーに送信します。これは、画像がめったに変更されない場合や遅延が大きな問題である場合はやり過ぎになる可能性があります (キャッシュされた画像がまだ良好な場合でも再検証応答を待つ必要があるため)。 . 画像の URL を変更する必要があります。
いつ使用するか:イメージが頻繁に変更される可能性がある場合、またはサーバー側スクリプトの関与なしにクライアントによって断続的に更新される必要があるが、キャッシュの利点が必要な場合に使用します。たとえば、数分ごとに不定期に画像を更新するライブ Web カメラをポーリングします。または、サーバーが静的な画像 URL でクエリ文字列を許可していない場合は、(1) または (2) の代わりに使用します。
[編集 2021: 最近の Chrome と Edge では機能しなくなりました: これらのブラウザーの内部 memcache は、フラグメント識別子を無視するようになりました (おそらく、Blink エンジンへの切り替え以来?)。ただし、以下の方法 (4) を参照してください。特にこれら 2 つのブラウザーでははるかに簡単になりました。そのため、この方法を (4) の簡略化されたバージョンと組み合わせて、これら 2 つのブラウザーをカバーすることを検討してください]。
(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 ページをドメインから<img>
読み込む必要があります。そうしないと、 を呼び出そうとしたときに「アクセスが拒否されました」というエラーが発生しますiframe.contentWindow.reload(...)
。[Chrome と Edge にもこれを行います]。
長所: DOM に必要な image.reload() 関数と同じように機能します。画像を通常どおりにキャッシュできます (必要に応じて将来の有効期限を設定しても、頻繁な再検証を回避できます)。クライアント側のコードのみを使用して、現在のページまたは他のページでその画像の URL を変更することなく、特定の画像を更新できます。
短所: Javascript に依存します。すべてのブラウザーで正しく動作することが 100% 保証されているわけではありません (ただし、FF27、Chrome33、および IE11 で正常に動作することをテストしました)。他の方法に比べて非常に複雑です。[2021 年編集: 最近の Chrome と Edge のサポートのみが必要な場合を除き、その場合は非常に簡単です]。
いつ使用するか:キャッシュしたい基本的に静的な画像のコレクションがあるが、それらを時々更新して、更新が行われたという視覚的なフィードバックをすぐに取得できる必要がある場合。(特に、たとえば AJAX で構築された一部の Web アプリのように、ブラウザー ページ全体を更新するだけでは機能しない場合)。また、(何らかの理由で)更新する必要がある画像を表示する可能性のあるすべての URL を変更できないため、方法(1)〜(3)が実行できない場合。(これら 3 つの方法を使用すると画像が更新されることに注意してください。ただし、別のページが適切なクエリ文字列またはフラグメント識別子なしでその画像を表示しようとすると、代わりに古いバージョンが表示される場合があります)。
これをかなり堅牢で柔軟な方法で実装する詳細を以下に示します。
あなたのウェブサイトの URL パスに空白の 1x1 ピクセルの .gifがあり、次の 1 行の PHP スクリプトもあるとします (クロスドメイン/img/1x1blank.gif
画像に強制更新を適用する場合にのみ必要で、任意のサーバーサイド スクリプト言語で書き換えることができます)。 、もちろん) URL パス:/echoimg.php
<img src="<?=htmlspecialchars(@$_GET['src'],ENT_COMPAT|ENT_HTML5,'UTF-8')?>">
次に、Javascript でこれらすべてを行う方法の現実的な実装を次に示します。少し複雑に見えますが、コメントがたくさんあります。重要な関数は forceImgReload() だけです。最初の 2 つは空白の画像と空白でない画像であり、独自の HTML で効率的に動作するように設計する必要があるため、次のようにコーディングします。あなたに最適です。それらの複雑さの多くは、Web サイトには不要な場合があります。
// 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 にいくつかの変更が必要になる場合があります。