画像をアップロードするための芸術的なアプローチ

アーティストとして、またWeb開発者として、時間の経過とともに、自分のギャラリーが必要になりました。 通常、ギャラリーには2つの主な機能があります。ショーケースの表示-すべて(または一部)の絵画-と1つの詳細表示。 両方の機能の実装は、ほぼすべての完成したギャラリーにあります。 しかし、完成したギャラリーの「使い古された」外観と標準のユーザーインターフェイスは、アーティストには適していません:)。 そして非標準-特別なアーキテクチャとコードの実装、画像の読み込みと表示が必要です。 この記事では、ディスプレイ自体とユーザーインターフェイスは省略されます。 主な焦点は、サーバーから画像をダウンロードすることです。 キュー、非同期ローダー、ブロブオブジェクトの処理、約束を果たすカスケード、および中断の可能性を使用した、制御されたロードの最終的な構成について説明します。







coffeeScriptで記述されたサンプルコード



タスク



  1. ディスプレイケースのすべての写真をダウンロードするには時間がかかります。 すべてを即座に表示することは不可能です。 そして、ユーザーがすぐに見る写真の主要な外観が可能です。

    したがって、タスクの1つは、目的の順序で画像をダウンロードする機能でした。 私のギャラリーは視覚的に中央に配置されているため、読み込み順序は遠心的です。最初に画面の中央に画像が読み込まれ、次に残りの部分が発散円になります。 したがって、小さな画面はすぐにいっぱいになり、大きな画面では最短時間で表示コントロールにアクセスできます(詳細表示への移動および移動のコントロールは中央の画像に集中します)。

  2. 別のタスクは、ページのデータの読み込みをすぐに開始できるように、ページ上のデータの読み込みを完全に待つことなく、ページの画像の読み込みを一時停止する機能でした。 これを行うには、リクエストの送信を一時停止し、ダウンロードされていない写真を覚えて、前のページに戻ってからダウンロードを再開する必要があります。





このために、3レベルのアーキテクチャが適用されました。

アプリケーション->ダウンロードマネージャー->非同期ブートローダー





アプリケーションレベル



アプリケーションは、ダウンロードして画面にレンダリングする必要がある画像のURLを連続して受け取ります。 URLの配信方法は興味深いものではありません。 将来の画像ごとに、アプリケーションは背景付きのimgまたはdiv DOMノードを作成します。

imgNode = ($ '<div>') .addClass('item' + num)
      
      





その後、彼はダウンロードマネージャーにタスクを渡し、サーバーから画像のURLを渡します。 マネージャーはプロミス( jQuery promise )を返します。プロミスが完了すると、ブラウザーメモリに保存されているロードされた画像データを含むblobクラスインスタンスのURLを取得します(URLはimgBlobUrlに移動します)。 これはHTML5で導入された新しい機能であり、ajaxリクエストの結果としてこの場合に受信したFileクラスまたはBlobクラスのインスタンスへのURLを作成できます

  loadingIntoLocal = @downloadMan.addTask image.url #       done,  imgNode    ,       ((imgNode) -> loadingIntoLocal.done (imgBlobUrl) -> imgNode.attr(src: imgBlobUrl) )(imgNode)
      
      







ダウンロードマネージャーレベル



ブートマネージャは、ジョブのキューを管理します。 各タスクは、ロードするURL、結果を取得したときに満たす約束、およびオプションで、初回以外のダウンロードのダウンロード試行回数を示します。 タスクが到着するとすぐに、それをキューに入れてプロミスを作成し、このプロミスをアプリケーションに返して、待つのが退屈しないようにします。 タスクを開始します。

  addTask : (url) -> downloading = new $.Deferred() task = { url: url, promise: downloading numRetries: 0 } @queue.push task @runTasks()
      
      





チャネルを最も効果的に使用するために、複数のXMLHttpRequest'ovを同時に実行します。 ブラウザでこれを行うことができます。 したがって、@ runTasks()メソッドは、パスの各時点で1つではなく、N個の要求があることを確認する必要があります。 私の場合、3つの「人力車」が実験的に選択されました。 無料の「人力車」がある場合は、キューから次のタスクを実行に渡します。

  runTasks: -> if (@curTaskNum < @maxRunningTasks) && !@paused @runNextTask()
      
      









「Rickshaw」は次のタスクを実行し、非同期ローダーの助けを借りてサーバーから画像をプルアップし、blob URLを受け取ります。

  runNextTask: -> task = @queue.shift() @curTaskNum++ downloading = @asyncLoader.loadImage task.url
      
      





ローダーがその約束を果たすとすぐに、「人力車」の1つが解放され、キューにまだジョブがある場合、@ runNextTask()メソッドは次を開始します。 同時に、アプリケーションに対してなされた約束が満たされたことをトップに報告します。

  downloading.done (imgBlobUrl) => task.promise.resolve imgBlobUrl @curTaskNum-- if @queue.length != 0 && !@paused @runNextTask()
      
      





マネージャーコード(簡易版)
  class DownloadManager constructor: -> @queue = [] @maxRunningTasks = 3 @curTaskNum = 0 @paused = false @asyncLoader = new AsyncLoader() addTask : (url) -> downloading = new $.Deferred() task = { url: url, promise: downloading numRetries: 0 } @queue.push task @runTasks() downloading runTasks: -> if (@curTaskNum < @maxRunningTasks) && !@paused @runNextTask() runNextTask: -> task = @queue.shift() @curTaskNum++ task.numRetries++ downloading = @asyncLoader.loadImage task.url downloading.done (imgBlobUrl) => task.promise.resolve imgBlobUrl @curTaskNum-- if @queue.length != 0 && !@paused @runNextTask() downloading.fail => if task.numRetries < 3 @addTask task.url pause: -> @paused = true resume: -> @paused = false @runTasks()
      
      







ただし、この一時停止の実装では、次のタスクを開始できるかどうかを示すフラグにより​​、ダウンロードの停止はおおまかに機能します。 ロードが3つのストリームのすべてのペアで行われたときに別のページへの遷移が発生した場合、現在のタスクの中断は発生せず、次のタスクは開始されません。

進行中のタスクに対してXMLHttpRequest.abort()を実行する一時停止の実装については、「よりスマートな一時停止」セクションで説明しています。



非同期ローダーレベル



非同期ローダーは、アーキテクチャの最下位レベルであり、XMLHttpRequestを送信し、バイナリイメージデータを受信して​​「クイックアクセスウェアハウス」に配置する「ステーション」です。





新しい人力車に「人力車」を装備し、その州のハンドラーを設置します。 生のバイトを含むArrayBufferオブジェクトとして利用可能なデータを受け取ることを期待していることに注意してください。 飛行中の「人力車」をサーバーに送ります。 そして、彼が戻ったらすぐに通知することを二階で約束します。

 class AsyncLoader loadImage: (url) -> xhr = new XMLHttpRequest() xhr.onprogress = (event) => ... #      xhr.onreadystatechange = => ... #     xhr.responseType = 'arraybuffer' xhr.open 'GET', url, true xhr.send() loadingImgBlob = new $.Deferred() return loadingImgBlob
      
      





回答が画像データとともに返されたら、それらからblobを作成します。 このオブジェクトのURLを取得するには、blobからobjectUrlを作成します。

  imgBlobUrl = window.URL.createObjectURL blob
      
      





「ローカルウェアハウス」内の結果の住所は、マネージャーに返されます。 これに写真をロードしました。

  xhr.onreadystatechange = => if xhr.readyState == 4 if (xhr.status >= 200 and xhr.status <= 300) or xhr.status == 304 contentType = xhr.getResponseHeader 'Content-Type' contentType = contentType ? 'application/octet-binary' blob = new Blob [xhr.response], type: contentType imgBlobUrl = window.URL.createObjectURL blob loadingImgBlob .resolve imgBlobUrl
      
      







よりスマートなポーズ



2番目のタスク(より緊急のタスクのための計画されたダウンロードの中断)を正しく解決するために、DownloadManagerアーキテクチャの平均レベルを変更します。 ダウンロードマネージャーは、メインタスクキューに加えて、まだ実行のために引き渡されていないタスクを含むキューが@enRouteキューの所有者になり、既に進行中のタスクを保存します。

  class DownloadManager constructor: -> @queue = [] @enRoute = [] @maxRunningTasks = 3 @curTaskNum = 0 @paused = false @asyncLoader = new AsyncLoader()
      
      





したがって、2種類のタスクがあります。最初のダウンロードと再開(写真が既にキューに入っている場合、読み込みを開始してから停止しました)。 また、Chromeは不足しているデータをダウンロードするだけで、ダウンロードは再開されません。 既に写真をキューにアップロードすることを約束していて、彼らがそれを待っている場合、それをキューの先頭に置きます。 まだダウンロードを開始していない場合は、最初にリクエストしてから、キューの最後に。 addTaskでの読み込みに関するpromiseオブジェクトの存在により、画像がすでに部分的にダウンロードされているかどうかを判断できます。

  addTask : (url, downloading) -> add = if !downloading then 'push' else 'unshift' downloading ?= new $.Deferred() #     ,   ,   ,      task = { xhr: null, #       XMLHttpRequest'  .     .  xhr      loadImage  asyncLoader'e url: url, promise: downloading numRetries: 0 } @queue[add] task @runTasks() return downloading
      
      





スターター@runTasks()は毎回、未処理のタスクがあるかどうか、タスクを完了する人がいるかどうか、一時停止していないかどうかを確認します。 もしそうなら、我々は働きます。

  runTasks: -> while (@queue.length != 0) && (@curTaskNum < @maxRunningTasks) && !@paused @runNextTask()
      
      





一時停止すると、パス(@enRoute)にあったすべてのリクエストがキャンセルされ(task.xhr。Abort () )、次回の配信のために再スケジュールされます。 この時間は、 resume ()がジョブスターターを再起動するとすぐに来ます。

  pause: -> @paused = true while @enRoute.length != 0 task = @enRoute.shift() task.xhr.abort() @addTask task.url, task.promise #      @curTaskNum-- resume: -> @paused = false @runTasks() runNextTask: -> task = @queue.shift() @enRoute.push task @curTaskNum++ task.numRetries++ { downloading, xhr } = @asyncLoader.loadImage task.url #         xhr,    ,      . task.xhr = xhr downloading.done (imgBlobUrl) => i = @enRoute.indexOf task @enRoute.splice i, 1 task.promise.resolve imgBlobUrl @curTaskNum-- @runTasks() downloading.fail => if task.numRetries < 3 @addTask task.url
      
      





制御されたローディングの全サイクルを説明しようとしました。 このアーキテクチャの作品の実例は、 ギャラリーにあります

テストのデモ 。 ダウンロードおよび実験用のデモコードはgithubにあります。

デモを試すときに、写真を提供する別のサービスを使用する場合は、異なるソース間でリソース共有を設定して(クロスオリジンリソース共有(CORS))、ブラウザーが別のドメインからダウンロードしたスクリプトにデータを送信できるようにする必要があります。 最も単純な場合、これはWebサーバーが応答でAccess-Control-Allow-Origin:*ヘッダーを返す必要があることを意味します。 これは、サーバーが他のドメインからのスクリプトがXHR要求を行うことを許可することをブラウザーに伝えます。 MDNの詳細をご覧ください。



All Articles