クライアントで画像をプレビューする:大食いブラウザとの戦い

みなさんこんにちは! 現在、次のタスクがあります。画像をダウンロードするためのインターフェイスを作成する必要があります。これにより、ダウンロードする前に小さな形式のサムネイルが生成されます。 現時点では、HTML5は地球上で本格的に稼働しており、これを実装する方法は非常に明確であるように思われます。 このテーマに関するロシア語の記事がいくつかあります(たとえば)。 しかし、一つだけあります。 そこで検討されたアプローチでは、ブラウザのメモリ消費に注意が払われませんでした。 そして消費は巨大な割合に達する可能性があります。 もちろん、小さなフォーマットの写真を5〜10個しかアップロードしない場合、すべてが通常の範囲内に残ります。 しかし、このインターフェースでは、最新のカメラ、石鹸入れを使用した場合よりも多くの形式の画像を一度にダウンロードできます。 そして、目の前で自由な記憶が溶け始めます。



まず、問題の規模を評価するために、このトピックのすべての記事にほとんど変更を加えずに説明したアプローチ実装し 、メモリ使用量を追跡してみます。 プレビューの作成を示すために、サンプルコードをできる限りシンプルにしようとしました。 ドラッグアンドドロップと読み込みの実装方法は、以前の記事でも見ることができます



コード
var listen = function(element, event, fn) { return element.addEventListener(event, fn, false); }; listen(document, 'DOMContentLoaded', function() { var fileInput = document.querySelector('#file-input'); var listView = document.querySelector('#list-view'); listen(fileInput, 'change', function(event) { var files = fileInput.files; if (files.lenght == 0) { return; } for(var i = 0; i < files.length; i++) { generatePreview(files[i]); } fileInput.value = ""; }); var generatePreview = function(file) { var reader = new FileReader(); reader.onload = function(e) { var dataUrl = e.target.result; var li = document.createElement('LI'); var image = new Image(); image.width = 100; image.onload = function() { // some action here }; image.src = dataUrl; li.appendChild(image); listView.appendChild(li); }; reader.readAsDataURL(file); }; });
      
      









テストでは、3648x2736ピクセルで平均容量4メガバイトの目立たない写真のフォルダーを使用しました。 現在のバージョンのブラウザーのセット:Chrome(31.0)、Yandex(13.12)、Firefox(26.0)、およびIE(11.0.1)。 さて、通常のタスクマネージャー(Win 8.1)。



そのため、フィールドで20枚の写真を選択します。 私たちは見ます:

ブラウザ メモリ消費量MB
クロム 994
ヤンデックス 1045
Firefox 1388
IE 1080




1)YandexとChromeはタブごとに個別のプロセスを保持しますが、FirefoxとIEは保持しないため、最後の2つでは、テストに直接関係しないオーバーヘッドも測定に含まれます。 2)すべての写真をロードしてから約20秒後に測定を行い(50 MB以内)、ブラウザがホットトラッキングでメモリを解放できるようにしました。 まだ大きすぎるボリュームを保持し続けます。 ページを更新または閉じた後、すべてのブラウザはメモリをゆっくりと通常サイズに解放します。



したがって、このような状況が決定的に私たちに合わないことは明らかです。 あなたが取ることができると思います...



シェルへの最初のアプローチ



私が最初に考えたのは、「すべての写真を並行してアップロードしようとすると、このようなオーバーランが発生した場合はどうなるでしょうか? 連続して試してみて、ブラウザに少し息を吹き込む機会を与えるかもしれません。」 さて、私たちは最も単純なキューを実装しようとしています。



コード
 // ...    ... var queue = []; var isProcessing = false; listen(fileInput, 'change', function(event) { var files = fileInput.files; if (files.lenght == 0) { return; } for(var i = 0; i < files.length; i++) { queue.push(files[i]); } fileInput.value = ""; processQueue(); }); var processQueue = function() { if (isProcessing) { return; } if (queue.length == 0) { isProcessing = false; return; } isProcessing = true; file = queue.pop(); var reader = new FileReader(); reader.onload = function(e) { var dataUrl = e.target.result; var li = document.createElement('LI'); var image = new Image(); image.width = 100; image.src = dataUrl; li.appendChild(image); listView.appendChild(li); isProcessing = false; processQueue(); }; reader.readAsDataURL(file); };
      
      









結果(同じ20枚の写真):

ブラウザ メモリ消費量MB
クロム 979
ヤンデックス 1119
Firefox 1360
IE 399




これはIEの場合にのみ役立つことがわかります。 まあ、何をすべきか-私たちはすべてのユーザーがIE以外のブラウザの使用を放棄することをお勧めします。 冗談。 さらに考える...



第二のアプローチ



いくつかのシャーマニズムのセッションの後、考えが思い浮かびます。「問題はおそらく、ブラウザーがメモリ内に広い画像を保持しなければならないことですが、実際には、プレビューのサイズまで画像を1回縮小するだけです。 通常のimgの代わりにcanvasを使用する場合、すでに縮小された画像をどこに置くか? " やってみましょう



コード
 var queue = []; var isProcessing = false; listen(fileInput, 'change', function(event) { // ... }); var processQueue = function() { // ...       file = queue.pop(); var reader = new FileReader(); reader.onload = function(e) { var dataUrl = e.target.result; var li = document.createElement('LI'); var canvas = document.createElement('CANVAS'); var ctx = canvas.getContext('2d'); var image = new Image(); listView.appendChild(li); image.onload = function() { var newWidth = 100; var newHeight = image.height * (newWidth / image.width); ctx.drawImage(image, 0, 0, newWidth, newHeight); li.appendChild(canvas); }; image.src = dataUrl; isProcessing = false; processQueue(); }; reader.readAsDataURL(file); };
      
      









結果(すべて同じ20枚の写真):

ブラウザ メモリ消費量MB
クロム 188(ピーク時に約800 MBに達しましたが、すぐに破棄しました)
ヤンデックス 201(ピーク時には〜1GBに達しましたが、Chromeのようにすぐに破棄しました)
Firefox 661(ピーク〜900。さらに1分間待機した後、300にスローされたことに注意する必要があります)
IE 103(ピーク〜260)




(IEを除くすべての人にとって)プロセスの消費量が多いにもかかわらず、ブラウザは少なくともすぐにメモリを解放し始めました。 これは喜ばしいことです。 しかし、最終的な勝利がまだ早すぎることを祝います。 もっとできると思います...



第三のアプローチ



さらに実験を行い、あまり成功していない実験の過程で、ObjectURL( creation and usage)などのAPIに遭遇すると、ブラウザのキャッシュに保存されたバイナリデータへのローカルリンクを作成し、それらを利用できることを思い出します。 理論的には、これは巨大なDataURLの処理を回避するのに役立ちます。 むしろ試す



コード
 // ...     var processQueue = function() { // ...     isProcessing = true; file = queue.pop(); var li = document.createElement('LI'); var canvas = document.createElement('CANVAS'); var ctx = canvas.getContext('2d'); var image = new Image(); listView.appendChild(li); image.onload = function() { var newWidth = 100; var newHeight = image.height * (newWidth / image.width); ctx.drawImage(image, 0, 0, newWidth, newHeight); URL.revokeObjectURL(image.src); li.appendChild(canvas); isProcessing = false; processQueue(); }; image.src = URL.createObjectURL(file); };
      
      









結果:

ブラウザ メモリ消費量MB
クロム 881
ヤンデックス 927
Firefox 140(ピーク〜860)
IE 36(ピーク〜70)




何を得たの? まあ、まず、IEでの優れた結果。 FFでは多少なりとも受け入れられます。 しかし、WebKitブラウザーでは、元に戻ったようです。 公平に言うと、すべてのブラウザで、感覚に応じて画像が純粋に高速に処理されるようになりましたが、同時にIEで短期間のフリーズが発生したことに注意してください。 FFとIEがURL.revokeObjectURL()を呼び出した後、すぐにリソースを正直に解放する可能性もあります。また、Webkitブラウザーはこれを行うのにある程度の時間を必要とします(メモリ不足の状況でこれを行う方が高速になる可能性さえあります)。 次の2つの方法でさらに進めることができます。1)アプローチを分割する-Webkit上のブラウザーでは、2番目のアプローチに戻ります(これですべてが明確になります-技術的な問題)。 2)あらゆる場所で3番目のアプローチを思い浮かべるようにしてください。 最後のオプションを試してみましょう...



4番目のアプローチ(および最後のアプローチ):そのような最適化は他にありますか?



少し絞って、いくつかの改善点を絞り込みます。 最初に、キューハンドラーからimg要素の作成を削除します。次に、同じ事前作成オブジェクトを再利用します。 将来的には、これにより、必要なWebブラウザーと新しいブラウザーのメモリ状況が大幅に改善されたと言えます。 2つ目は、これは古くから知られているトリックです。setTimeout()を使用して後続の各処理を少し延期します。これは、短期間のフリーズの状況を改善するのに役立ちました。 結果は次のとおりです



コード
 //    var listen = function(element, event, fn) { return element.addEventListener(event, fn, false); }; listen(document, 'DOMContentLoaded', function() { var fileInput = document.querySelector('#file-input'); var listView = document.querySelector('#list-view'); var queue = []; var isProcessing = false; var image = new Image(); //     img var imgLoadHandler; listen(fileInput, 'change', function(event) { var files = fileInput.files; if (files.lenght == 0) { return; } for(var i = 0; i < files.length; i++) { queue.push(files[i]); } fileInput.value = ""; processQueue(); }); var processQueue = function() { if (isProcessing) { return; } if (queue.length == 0) { isProcessing = false; return; } isProcessing = true; file = queue.pop(); var li = document.createElement('LI'); var canvas = document.createElement('CANVAS'); var ctx = canvas.getContext('2d'); //      image.removeEventListener('load', imgLoadHandler, false); imgLoadHandler = function() { var newWidth = 100; var newHeight = image.height * (newWidth / image.width); ctx.drawImage(image, 0, 0, newWidth, newHeight); URL.revokeObjectURL(image.src); li.appendChild(canvas); isProcessing = false; setTimeout(processQueue, 200); //    }; listView.appendChild(li); listen(image, 'load', imgLoadHandler); image.src = URL.createObjectURL(file); }; });
      
      









テスト:

ブラウザ メモリ消費量MB
クロム 103(ピーク〜150)
ヤンデックス 113(ピーク〜150、Chromiumなど)
Firefox 107(ピーク〜510)
IE 40(そしてそれは上に上昇しませんでした。まるで仕事がまったく行われていないかのように)




同時に、同じサイズの100枚の写真でもテストします。

ブラウザ メモリ消費量MB
クロム 98(ピーク〜150)
ヤンデックス 150(ピーク〜180)
Firefox 104(ピーク〜520)
IE 40(すべて同じ40MB!)




処理される画像の数を増やしても、実際には消費量は増えないことがわかります。 これはおそらく停止します。



おわりに



認めるために、非常に最初の調査の後、ある時点で、現在の状況では、過剰なメモリオーバーランなしにこの機会を実現することは不可能だと思いました。 それでも、(一度に100枚の画像をアップロードしたい)ユーザーの仮想ネットブックを停止するのはugいです。 しかし、これらの疑問を克服できたのは素晴らしいことです:)



そこで、いくつかのポイントを見つけることができました。 回数:DataURLの使用は、非常に小さな形式の画像の操作にのみ適しています(大きな形式の場合、2つのメソッドのみで構成されるobjectURL APIを使用することをお勧めします)。 2番目:多数のImageオブジェクトを作成するときは注意が必要です。 3番目:すべての処理を一度に実行しないでください。



さて、この小さな研究は不可解にもブラウザーの比較につながったので、それぞれを個別に見ていきましょう。



Firefoxは負けましたか?


ピーク時の消費量は他と比べて際立っていますが、この状況はまだ許容範囲内であると思います。 まず(上記では言及していません)、測定からさらに30秒後に、メモリは60MBに低下しました。これは、Webベースのものに比べてさらに低い値です。 第二に、深刻な不足の状況では、Firefoxは処理中に定期的にメモリを消去し、最終的にはピーク時でもそれほど食い尽くしません。 一般的に、我々は出発しました。



IEでみんなに追いつきましょう!


これも冗談です:)しかし客観的に言えば、IEは通常のブラウザをダウンロードするための単なるツールではなく、少なくとも1つ以上の適切なブラウザであることを認めなければなりません。



Yandex.Browserはお兄さんより少し遅れていますか?


そうは思いません そして、ここに理由があります。事実は、私が今それを主なものとして使用しているということです。したがって、実験をクリスタルクリアと呼ぶことができません いくつかのプラグイン、履歴、同期-これらすべてがこのわずかな遅れを引き起こす可能性があります。



そしてOperaはどこにありますか?


この実験のために、12番目のバージョンを個別に配置したくありませんでした。 他の誰かによってまだ使用されているという事実にもかかわらず、まもなくこの数の忠実なファンは、アップグレードするか、別のブラウザに移行する必要があります。 そして、新しいWebkitについて-状況がYandexやChromeに似ていると信じるには、あらゆる理由があります。



UPD:第12オペラでも同じようにチェックされました。 結果は次のとおりです。3番目(したがって4番目)のアプローチは機能しません。 2番目のアプローチは、ピーク時に500MB、処理後に300MBを消費します。



しかし、Safariはどうですか?


勝ったとき、これは珍しい獣ですが、ポピーで私は最終版をテストしました(すべて同じ20枚の写真で)。 処理中、メモリ消費はまったく増加しませんでした。



モバイルブラウザについてはどうですか?


iPhone 5SのSafariでもチェックされています。 短期間のフリーズが観察されますが、同時に空きメモリの量は実際には減少していません。 各プロセスが個別に予約している量をどうにかして見ることができるかどうか、すぐにはわかりませんでした。コメントで誰かが教えてくれたらありがたいです。 残念ながら、Androidデバイスは手元にありませんでした。 おそらく、誰かが自分でチェックして結果を共有するのが面倒ではありません。



ご清聴ありがとうございました。 誰かが同様の研究に時間を無駄にしないように記事を助けてくれることを願っています。 過去の祝日には、%username%!



All Articles