JavaScript進捗管理の詳しい説明

JavaScript進捗管理の詳しい説明

序文

プログラムを作成するときに、読み込みの進行状況やアップロードの進行状況など、進行状況を表示する必要に迫られることがよくあります。
最も一般的な実装方法は、完了数量 (loadedCount) と合計数量 (totalCount) を記録し、進捗状況を計算することです。
この方法は単純かつ大雑把で、実装は簡単ですが、拡張が難しく、すべての loadedCount と totalCount を維持する場所が必要です。
この記事では、上記の実装方法をベースに、より簡単にスケーラブルな進捗管理方法を実装します。

質問

私は WebGL アプリケーションを作成しており、アプリケーションのプリロード フェーズ中にロードの進行状況を計算する必要があります。
読み込まれるコンテンツには、モデル リソース、マップ リソース、スクリプト リソースなどが含まれます。
モデル リソースにはマテリアル リソースが含まれ、マテリアル リソースにはテクスチャ リソースが含まれます。
これを図で表すと、構造は次のようになります。

+-------------------------------------------------------------+
| |
| リソース |
| |
| +----------+ +-----------------+ +-----------------+ |
| | スクリプト1 | | モデル1 | | モデル2 | |
| +----------+ | | | | |
| | -------------+ | | -------------+ | |
| +----------+ | |model1.json | | | |model2.json | | |
| | スクリプト2 | | +-------------+ | | +-------------+ | |
| +----------+ | | | | |
| | +-------------+ | | +-------------+ | |
| +----------+ | | 素材1 | | | | 素材1 | | |
| | テクスチャ1 | | | +--------+ | | | | +--------+ | | |
| +----------+ | | |テクスチャ1| | | | | |テクスチャ1| | | |
| | | +--------+ | | | | +--------+ | | |
| +----------+ | | +--------+ | | | | +--------+ | | |
| | テクスチャ2 | | | |テクスチャ2| | | | | |テクスチャ2| | | |
| +----------+ | | +--------+ | | | | +--------+ | | |
| | +-------------+ | | +-------------+ | |
| | | | | |
| | +-------------+ | | +-------------+ | |
| | | マテリアル2 | | | | マテリアル2 | | |
| | +-------------+ | | +-------------+ | |
| +-----------------+ +-----------------+ |
| |
+-------------------------------------------------------------+

ここで前提となるのは、リソースをロードするときに、ロードが完了したと見なされる前に、リソースとそれが参照するリソースがすべてロードされていることを確認する必要があるということです。
この前提に基づいて、サブリソースの読み込みの進行状況がすでに含まれている進行状況を返す onProgress インターフェイスを実装しました。
コードに翻訳すると:

クラスアセット{
    ロード(onProgress) {
        新しいPromise((resolve) => {を返す
            if (typeof onProgress !== 'function') {
                onProgress = (_p) => { };
            }

            loadedCount = 0 とします。
            let totalCount = 10; // 注意: デモ用です
            onLoaded = () => {
                ロードされたカウント++;
                onProgress(読み込まれたカウント / 合計継続時間);
                読み込まれたカウントが合計カウントの場合、解決します。
            };

            約束.すべて(
                this.refAssets.map(asset => asset.load().then(onLoaded))
            );
        });
    }
}

このインターフェースができたので、loadedCount と totalCount のグローバルメンテナンスという形式を引き続き使用すると、扱いが非常に面倒になります。
次にこの記事で紹介するのは回避策です。

原理

基本的な考え方は分割統治です。大きなタスクを複数の小さなタスクに分割し、すべての小さなタスクの進行状況を個別に計算し、最後にすべての小さなタスクの進行状況を結合して全体の進行状況を取得します。
次の図に示すように:

+--------------------------------------------------------------------+
| |
| |
| 総進捗状況 |
| |
| +---------+----------+-----------+----------+---------+---------+ |
| | スクリプト1 | スクリプト2 | テクスチャ1 | テクスチャ2 | モデル1 | モデル2 | |
| | (0~1) | (0~1) | (0~1) | (0~1) | (0~1) | (0~1) | |
| +---------+----------+-----------+----------+---------+---------+ |
| |
| モデル1 |
| +-------------+------------------------+-----------+ |
| | model1.json | マテリアル1 | マテリアル2 | |
| | (0~1) | (0~1) | (0~1) | |
| +------------------------+------------------------+ |
| | テクスチャ1 | テクスチャ2 | |
| | (0~1) | (0~1) | |
| +----------+-------------+ |
| |
| モデル2 |
| +-------------+------------------------+-----------+ |
| | model2.json | マテリアル1 | マテリアル2 | |
| | (0~1) | (0~1) | (0~1) | |
| +------------------------+------------------------+ |
| | テクスチャ1 | テクスチャ2 | |
| | (0~1) | (0~1) | |
| +----------+-------------+ |
| |
+--------------------------------------------------------------------+

この原則に基づいて、すべてのリソースの現在の読み込み進行状況をリストに保存し、onProgress がトリガーされるたびにマージ操作を実行して全体の進行状況を計算することで、進行状況が実装されます。

var 進行 = [
  0, // スクリプト1,
  0, // スクリプト2,
  0, // テクスチャ1,
  0, // テクスチャ2,
  0, // モデル1,
  0, // モデル2
];

関数 onProgress(p) {
    // TODO: progresses[??] = p;
    progresses.reduce((a, b) => a + b, 0) / progresses.length を返します。
}

しかし、ここには難しさがあります。onProgress コールバックがトリガーされたとき、リスト内のどの項目を更新する必要があるかをどのように知るのでしょうか?
JavaScript のクロージャ機能を使用すると、この機能を簡単に実装できます。

var 進捗 = [];
関数add(){
    進行します。push(0);
    var インデックス = progresses.length - 1;

    関数onProgress(p)を返す{
        progresses[インデックス] = p;
        減らす();
    };
}

関数reduce() {
    progresses.reduce((a, b) => a + b, 0) / progresses.length を返します。
}

クロージャを使用してリソースのインデックスを保持します。onProgress がトリガーされると、リスト内の対応する項目の進行状況をインデックスに従って更新できます。正しい進行状況は最終マージ時に計算できます。
残っているのは、すべてのコードを統合してテストすることだけです。

テスト

次のコードを使用して、読み込みプロセス全体をシミュレートできます。

クラスアセット{
    コンストラクター(totalCount) {
        this.loadedCount = 0;
        this.totalCount = 合計カウント;
        this.timerId = -1;
    }

    ロード(onProgress) {
        if (typeof onProgress !== 'function') {
            onProgress = (_p) => { };
        }

        新しいPromise((resolve) => {を返す
            this.timerId = setInterval(() => {
                this.loadedCount++;
                onProgress(this.loadedCount / this.totalCount);
                (this.loadedCount === this.totalCount)の場合{
                    タイマーIDをクリアします。
                    解決する();
                }
            }, 1000);
        });
    }
}

クラス Progress {
    コンストラクター(onProgress) {
        onProgress は、次の式で定義されます。
        this._list = [];
    }

    追加() {
        this._list.push(0);

        定数インデックス = this._list.length - 1;

        戻り値 (p) => {
            this._list[インデックス] = p;
            これをreduce()します。
        };
    }

    減らす() {
        定数 p = Math.min(1, this._list.reduce((a, b) => a + b, 0) / this._list.length);
        this.onProgress(p);
    }
}

const p = 新しい Progress(console.log);
定数asset1 = 新しいアセット(1);
定数asset2 = 新しいアセット(2);
定数asset3 = 新しいアセット(3);
定数asset4 = 新しいアセット(4);
定数asset5 = 新しいアセット(5);

Promise.all([
    アセット1.load(p.add())、
    アセット2.load(p.add())、
    アセット3.load(p.add())、
    アセット4.load(p.add())、
    アセット5.load(p.add())、
]).then(() => console.log('すべてのリソースが読み込まれました'));

/**
  出力 Promise { <state>: "pending" }
  
  0.2 
  0.3 
  0.366666666666666664 
  0.416666666666666663 
  0.456666666666666667 
  0.55666666666666668 
  0.6233333333333333 
  0.6733333333333333 
  0.7133333333333333 
  0.78 
  0.83000000000000001 
  0.8699999999999999 
  0.9199999999999999 
  0.96 
  1 
  すべてのリソースが読み込まれました 
 */

この方法の利点は、loadedCount と totalCount のグローバル管理を回避し、この部分の作業をリソースの内部管理に戻すことができることです。必要なのは、大きなタスクをマージして計算することだけです。

欠点も明らかであり、onProgress インターフェースを統一する必要があります。既存のプロジェクトを進めるのは非常に難しいため、新規プロジェクトや小規模プロジェクトに適しています。

以上がJavaScript進捗管理の詳細です。JavaScript進捗管理の詳細については、123WORDPRESS.COMの他の関連記事にも注目してください!

以下もご興味があるかもしれません:
  • jsは矢印で進行プロセスを実現します
  • JS はプログレスバーの動的な読み込み効果を実現します
  • 進捗バー効果を実現するための JavaScript+CSS
  • JSは制御可能なプログレスバーを実装します
  • jsを使用してシンプルなプログレスバー効果を実現する
  • Node.js はプログレスバー付きの複数ファイルのアップロードを実装します
  • js+HTML5 キャンバスでシンプルな読み込みバー (プログレスバー) 関数を実装する例
  • ネイティブ js を使用して、進行状況を監視するファイルアップロード プレビュー コンポーネントを実装する方法を 3 分で教えます。
  • JS でダウンロード進行状況バーと再生進行状況バーを実装するためのコード

<<:  Windows 10 での MySQL 5.7.21 winx64 のインストールと設定方法のグラフィック チュートリアル

>>:  Nginx操作応答ヘッダー情報の実装

推薦する

Homebrewを使用してMacにMySQLをインストールするときにログインできない問題を解決する

お使いのコンピュータが Mac の場合、homebrew を使用して MySQL をインストールする...

JavaScript が Jingdong の虫眼鏡効果を模倣

この記事では、Jingdongの虫眼鏡効果を実現するためのJavaScriptの具体的なコードを紹介...

Nginx で Angular プロジェクトを展開する際の落とし穴

コンパイル後にAngularプロジェクトをNginxにデプロイする方法をオンラインで検索すると、ほと...

Linux に JDK1.8 をインストールするための詳細なチュートリアル

1. 設置前の清掃 rpm -qa | grep jdk rpm -qa | grep gcj yu...

tomcatでcatalina.outログをカットする3つの方法の詳細な説明

1. ログセグメンテーションのためのLog4j 1) log4j-1.2.17.jar、tomcat...

div+css3 を使用して背景グラデーション ボタンを実装するためのサンプル コード

フロントエンド ページの需要が増加し続けるにつれて、一部のシーンではグラデーションの背景要素が必要に...

MySQL 1対多関連クエリのページングエラー問題の解決方法

XML価格照会のクエリデータにはリストが含まれているため、コレクションが必要です <結果マップ...

MySQL は正常に起動するがポートをリッスンしない場合の解決策

問題の説明MySQL が正常に起動しました。以下に示すように、 ps -ef |grep mysql...

Vue3.0 における Ref と Reactive の違いの詳細な分析

目次参照と反応参照反応的RefとReactiveの違いshallowRef と shallowRea...

Docker-compose は Docker プライベート ウェアハウスのステップを迅速に構築します

docker-compose.ymlを作成し、次の内容を入力します。 バージョン: '3&#...

MySQL における Datetime と Timestamp の使用の概要

目次1. MySQL で現在の時刻を表現するにはどうすればよいでしょうか? 2. TIMESTAMP...

Nginx Rewrite の使用シナリオと設定方法の分析

Nginx Rewriteの使用シナリオ1. URL アドレスジャンプ。たとえば、ユーザーが pm....

複数のパッケージソースから同時にパッケージをロードするようにnpmを設定する方法

目次1. ローカルストレージを構築する2. npmパッケージを作成し、プライベートリポジトリにアップ...

Vue Element フロントエンドアプリケーション開発: Vuex での API ストアビューの使用

目次概要1. フロントエンドとバックエンドの分離とWeb APIの優先ルート設計2. Axiosネッ...

MySQL 8.0.13 zipパッケージのインストール方法について

MySQL 8.0.13 にはデフォルトでデータ フォルダがあります。このフォルダを削除する必要があ...