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