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操作応答ヘッダー情報の実装

推薦する

Vue の関連ページへのマルチレベルジャンプ (ページドリルダウン) 機能の完全な例

背景プロジェクト開発プロセスでは、前のページから次のページにジャンプする必要に迫られることがよくあり...

MySQLに絵文字表現を保存する詳細な手順

原因: java.sql.SQLException: 列の文字列値が正しくありません: '\...

MYSQLは継続サインイン機能を実装しており、サインイン後1日経過すると最初から開始します(SQL文)

1. テストテーブルを作成する テーブル `testsign` を作成します ( `userid`...

React 高階コンポーネント HOC 使用方法の概要

HOCを紹介する一文高階コンポーネント (HOC) とは何ですか? 公式ドキュメントによると、「高階...

IDEA が MySQL データベースに接続できない問題の 6 つの解決策

この記事では、IDEA が MySQL データベースに接続できない問題に対する 6 つの解決策を主に...

MySQLリモートアクセスの設定方法をステップバイステップで説明します

序文MySQL データベースを使用する場合、クライアントはデータベース サーバーにリクエストを送信す...

Vue.js を学ぶ際に遭遇する落とし穴

目次クラス void のポイントES6 矢印関数ヴュートファイvue-cli非同期と同期実行と展開ヒ...

JS で単一ファイルコンポーネントを実装する方法

目次概要単一ファイルコンポーネント基本概念シンプルなローダーコンポーネントコンテンツの解析コンポーネ...

太陽系の惑星のアニメーション効果を実現するHTML+CSS3コード

太陽系の 8 つの惑星(衛星を除く)のアニメーションを作成します。すべての惑星は太陽の周りを回ってい...

Kubernetes ポッドオーケストレーションとライフサイクルの詳細な説明

目次K8Sマスター基本アーキテクチャポッドオーケストレーションコンセプトPod オブジェクトのプロパ...

JavaScript axiosのインストールとパッケージ化のケースの詳細な説明

1. axiosプラグインをダウンロードする cnpm インストール axios -S 2. mai...

JS上級編ES6の6つの継承方法

目次1. プロトタイプチェーン継承2. コンストラクタによる継承3. 組み合わせ継承4. プロトタイ...

CSS オーバーフローラップの新しいプロパティ値をどこでも使用

1.まず、overflow-wrap属性を理解する CSS のoverflow-wrapプロパティは...

MySQL5.7.03 上位バージョンから MySQL 5.7.17 への置き換えインストール プロセスと見つかった問題の解決策

1. インストール方法は? 1. [実行] -> [cmd] と入力して、小さな黒いウィンドウ...

React Hooksを使用する際のよくある落とし穴

React Hooks は React 16.8 で導入された新しい機能で、クラスを使用せずに状態や...