JavaScript を使用してタイムラインとアニメーション効果を実装するためのサンプル コード (フロントエンドのコンポーネント化)

JavaScript を使用してタイムラインとアニメーション効果を実装するためのサンプル コード (フロントエンドのコンポーネント化)

前回の記事「JSX を使用したカルーセル コンポーネントの実装」では、「基本的な」カルーセル コンポーネントを実装しました。なぜ「財団」と呼ぶのでしょうか?弊社のカルーセル コンポーネントの機能を満たすことはできているようですが、まだ完成していない欠陥が多数あります。

これには 2 つの機能が実装されていますが、1 つは自動カルーセル、もう 1 つはジェスチャ ドラッグです。しかし、実際には、まだ実際に使える状態には程遠いのです。

まず、自動カルーセルとドラッグはシームレスに接続できません。つまり、ドラッグを終了した後、カルーセルは自動的に回転し続ける必要があります。私たちはまだこれを達成していません。ドラッグ自体にも細かい問題があります。たとえば、現在はマウスのドラッグ イベントのみがサポートされており、タッチ スクリーンのドラッグはサポートされていません。これも、Web ページの開発中に直面しなければならない問題です。

次に、アニメーションはCSS Animationを使用して実装されており、カスタマイズや対応する変更は一切ありません。

それでは、アニメーション ライブラリを一緒に実装してみましょう。ただし、アニメーション ライブラリを実装する前に、アニメーション ライブラリにタイムライン ライブラリが必要です。この記事では、まずタイムライン クラスと、このタイムラインを使用するための基本的なアニメーション クラスを実装する方法について説明します。

コードのクリーニング

まず、前に書いた Carousel コンポーネントのコードがすでに非常に複雑であることがわかったので、それをカプセル化する必要があります。ここでは、それを別の JavaScript ファイルに配置します。

プロジェクトのルート ディレクトリにcarousel.jsを作成し、 main.js内のすべてのカルーセル コンポーネント関連のコードを carousel.js に移動します。

carousel.js では、 Componentをインポートし、Carousel クラスをエクスポートするだけです。コード構造は次のとおりです。

'./framework.js' から { コンポーネント } をインポートします。

export class Carousel extends Component ** Carousel 内のコード */

最後に、main.js に Carousel コンポーネントを再インポートできます。

'./framework.js' から Component、createElement をインポートします。
'./carousel.js' から { Carousel } をインポートします。

ギャラリー = [
 'https://source.unsplash.com/Y8lCoTRgHPE/1600x900',
 'https://source.unsplash.com/v7daTKlZzaw/1600x900',
 'https://source.unsplash.com/DlkF4-dbCOU/1600x900',
 'https://source.unsplash.com/8SQ6xjkxkCo/1600x900',
];

a = <Carousel src={gallery} /> とします。

// document.body.appendChild(a);
ドキュメントの本文にマウントします。

コードを整理したら、タイムライン ライブラリの作成を開始できます。このタイムラインはアニメーション ライブラリの一部なので、アニメーション ライブラリの JavaScript ファイル ( animation.js ) に配置します。

このタイムラインを使用して後続のアニメーションライブラリを実装する必要がありますが、アニメーションには非常に重要な概念である「フレーム」があります。

最も基本的なアニメーション機能は、フレームごとにイベントを実行することです。


JavaScript の「フレーム」

アニメーションを実現するには「フレーム」が必要なので、まず JavaScript でのいくつかのフレーム処理ソリューションを理解する必要があります。

人間の目が認識できるアニメーションの最高周波数は60 フレームです。

学生の中にはアン・リー監督の映画を見たことがある人もいるかもしれません。たとえば、「ビリー・リンのロング・ハーフタイム・ウォーク」は、120 フレームで撮影され再生された世界初の映画です。

また、フレームレートが2倍になったため、多くの場所が非常にスムーズに感じられるでしょう。しかし、一般的に言えば、モニターを含む当社のゲームはすべて 60 フレームをサポートしています。モニターの設定では 70 または 80 フレームが表示されることがありますが、一般的なソフトウェアでは 60 フレームに揃えられます。

1000 ミリ秒 (1 秒) に 60 フレームが必要な場合、1 フレームは何ミリ秒になりますか?つまり、1000 / 60 = 16.666 1000 / 60 = 16.666 1000/60=16.666 なので、16 ミリ秒が 1 フレームの時間におおよそ相当します。

このため、通常はフレームの長さとして 16 ミリ秒を使用します。


「フレーム」の実装方法

次に、JavaScript で「フレーム」を実装するために使用できるメソッドを分析しましょう。

1. 間隔を設定する

1 つ目は、カルーセルを作成するときに実際に使用したsetIntervalです。ロジックをすべてのフレームで実行させるには、次のようにします。

setInterval(() =>{/** 1 フレームで何が起こるか*/, 16)

ここで設定される時間間隔は 1 フレームの長さである 16 ミリ秒です。

2.タイムアウトを設定する

setTimeout を使用して、1 つのフレーム内でイベントを繰り返し処理することもできます。しかし、setTimeout は一度しか実行されないためです。したがって、後で繰り返し呼び出せるように関数名を付ける必要があるのです。

通常、アニメーションのフレームとして使用されるこの種の setTimeout はtickと呼ばれます。英語のtickは時計の秒針が1秒進むときに鳴る音です。この音は1フレーム/1秒を表す言葉としても使われます。

これを使用する方法は、ティック関数を定義し、ロジック/イベントを実行させることです。次に、setTimeout を使用して、再度実行する前に 16 ミリ秒の遅延を追加します。

ティック = () => {
	/** ロジック/イベント*/
 setTimout(ティック、16);
}

3. アニメーションフレームをリクエストする

最後に、最新のブラウザはrequrestAnimationFrame (RAF とも呼ばれます) をサポートしています。これはアニメーションを記述するときによく使用され、フレームの継続時間を定義する必要はありません。

ブラウザに次のフレームの実行を要求すると、RAF に渡されたコールバック関数が実行されます。この関数の実行時間はブラウザのフレームレートに関係します。

したがって、ブラウザのフレーム レートの削減や周波数の削減操作を実行する場合は、ブラウジングのフレーム レートとともに RAF を削減できます。

使い方も非常に簡単です:

ティック = () => {
	/** ロジック/イベント*/
 setTimout(ティック、16);
}

したがって、一般的にはこれら 3 つのソリューションが最もよく使用されます。ほとんどのユーザーが最新のブラウザを使用している場合は、 requestAnimationFrameが推奨されます。

「なぜ setInterval を使わないのですか?」 setInterval は比較的制御しにくいため、ブラウザは設定した 16 ミリ秒に従ってそれを実行するでしょうか?それは言いにくいですね。

もう 1 つは、ティックが適切に記述されていない場合、setInterval がバックログされる可能性があることです。固定の 16 ミリ秒ループで実行されるため、前の間隔のコードが実行されたかどうかに関係なく、2 番目の間隔のコードは間隔キューに入ります。これもブラウザの基盤となる実装によって異なります。ブラウザごとに異なる戦略が選択される場合があります。

ここで実装するアニメーションライブラリは、古いブラウザとの互換性を考慮する必要がないためです。ここでは requestAnimationFrame を使用することを選択します。

次のタイムライン ライブラリでは、requestAnimationFrame を使用して自己繰り返し操作を実行します。

ここでは、requestAnimationFrame に対応する cancelAnimationFrame についても言及する必要があります。 requestAnimationFrame を格納する変数を宣言すると、この変数を cancelAnimationFrame に渡してアニメーションを停止できます。

ティック = () => {
	ハンドラーを requestAnimationFrame(tick); に設定します。
 アニメーションフレームをキャンセルします(ハンドラ);
}

こうすることで、リソースの無駄をある程度回避できます。


タイムラインの実装

冒頭で述べたように、アニメーションを作成するときは、 tickTimelineにパッケージ化する必要があります。

次に、この Timeline クラスを一緒に実装してみましょう。通常、タイムラインはstartのみが必要であり、 stop状態はありません。タイムラインは必ず最後まで再生されるため、途中で停止することはありません。

ただし、一時pauseresumeの組み合わせがあります。この状態のグループもタイムラインにおいて非常に重要な機能です。たとえば、多数のアニメーションを作成する場合、実行のためにそれらすべてを同じアニメーション タイムラインに配置する必要があります。実行プロセス中に、これらすべてのアニメーションの再生を一時停止および再開できます。

もう 1 つはrate (再生速度) ですが、これはすべてのタイムラインで使用できるわけではありません。 rate には 2 つのメソッドがあり、1 つはset 、もう 1 つはgetです。再生速度は倍数なので、アニメーションを早送りしたり遅くしたりできます。

このアニメーションライブラリを設計する際には、 resetと呼ばれる非常に重要な概念もあります。これにより、タイムライン全体がクリーンアップされ、一部のタイムラインを再利用できるようになっています。

これはより高度なタイムライン関数であるため、このチュートリアルで実装されている設定レートと取得レートは実装されません。これを実現したいのであれば、関連する知識をたくさん学ぶ必要があります。しかし、 pauseresumeカルーセルにとって非常に重要なので、ここで実装する必要があります。

ここまでお話ししましたが、始めましょう! 〜

開始関数の実装

開始メソッドでは、 tickを開始するプロセスがあります。ここでは、このチェックをプライベート メソッドにする (非表示にする) ことを選択します。そうしないと、誰でもこのティックを呼び出すことができ、外部ユーザーによって Timeline クラスの状態システム全体が簡単に破壊される可能性があります。

では、ダニを完璧に隠すにはどうすればいいのでしょうか? animation.js ファイルのグローバル スコープでTICKという定数を宣言します。そして、 Symbolを使用してチェックマークを作成します。この方法では、animation.js でティックを取得できることを除いて、ティック シンボルを他の場所で取得することはできません。

同様に、tick 内の requestAnimationFrame も、それを保存するためのグローバル変数TICK_HANDLERを作成できます。この変数もシンボルでラップされるため、このファイル内でのみ使用できます。

Symbol にあまり詳しくない人にとっては、実際にはそれを一種の「特殊文字」として理解することができます。シンボルに渡される両方のキーを「tick」と呼ぶ場合でも、作成される 2 つの値は異なります。これは Symbol の機能です。

実際、以前の記事「フロントエンドの高度な機能」でも Symbol について詳しく説明し、使用しました。たとえば、EOF (End Of File) ファイル終了シンボルを表すために Symbol を使用しました。したがって、オブジェクトへのキーであることは、Symbol の唯一の用途ではありません。Symbol のユニークな特徴は、その存在意義の 1 つです。

これら 2 つの定数を使用して、Timeline クラスのコンストラクターでティックを初期化できます。

Tick を初期化した後、 start関数でグローバルTICKを直接呼び出すことができます。こうすることで、タイムライン内の時間は 60 フレームの再生速度で実行され始めます。

最終的なコードは次のようになります。

const TICK = シンボル('tick');
const TICK_HANDLER = Symbol('tick-handler');

エクスポートクラスタイムライン{
 コンストラクタ() {
 this[TICK] = () => {
 console.log('ティック');
 アニメーションフレームをリクエストします(this[TICK]);
 };
 }
 始める() {
 this[TICK]();
 }
 一時停止() {}
 再開する() {}
 リセット() {}
}

この部分を完了したら、この Timeline クラスを main.js に導入して試すことができます。

'./animation.js' から { Timeline } をインポートします。

tl = new Timeline();

tl.start(); 

コードをビルドしてブラウザで実行します。コンソールでティックが正常に実行されていることを確認できます。これは、タイムラインの現在のロジックが正しく記述されていることを示しています。

これまで、非常に基本的なタイムライン操作を実装しました。次に、タイムラインをテストするための簡単な Animation クラスを実装しましょう。

アニメーションクラスの実装

次に、Tick にアニメーションを追加して実行します。

作成したタイムラインは、最終的にカルーセルのアニメーションで使用されます。カルーセル上のアニメーションは「属性アニメーション」と呼ばれます。

オブジェクトのプロパティをある値から別の値に変更しているためです。

属性アニメーションとは対照的に、フレームアニメーションは、1 秒ごとに 1 つの画像が表示されることを意味します。コマアニメーションといえば、宮崎駿監督の名作『となりのトトロ』や『天空の城ラピュタ』などのアニメーションを誰もが知っているのではないでしょうか。これらのアニメーションはすべて宮崎駿氏によって1枚ずつ描かれ、1フレームごとに1枚の絵が再生されます。高速再生プロセスでは、画像内の人物や物体が動いているのを見ることができます。アニメの時代よりもさらに以前にもアニメーションは存在しており、それを古代人はゾートロープと呼んでいました。

上記のアニメーションは属性を通じて実行されるものではありません。しかし、ブラウザで行うことのほとんどは属性アニメーションです。各アニメーションには、初期プロパティ値と終了プロパティ値があります。

アニメーションの理論を理解した後、この部分のロジックの実装を開始できます。まず、アニメーションのロジックはタイムラインから比較的独立しているため、アニメーションを別のクラスにカプセル化できます。 (フロントエンドのコンポーネント化に関する今後の記事では、アニメーション ライブラリの機能をさらに強化する予定です。 )

エクスポートクラス Animation {
 コンストラクタ() {}
}

まず、アニメーションを作成します。次のパラメータが必要です。

  • object : アニメーション化する要素オブジェクト
  • property : アニメーション化するプロパティ
  • startValue : アニメーションの開始値
  • endValue : アニメーションの終了値
  • duration : アニメーションの継続時間
  • timingFunction : アニメーションと時間曲線

ここで注意する必要があるのは、渡されるプロパティには通常、 px (ピクセル) などの単位が付いていることです。 startValueendValue JavaScript の値である必要があるためです。したがって、完全なアニメーションが必要な場合は、さらに多くのパラメータを渡す必要があります。

ただし、ここでは後で追加するのではなく、まずは簡単なアニメーションを実装しましょう。

Animation オブジェクトを初期化するときには、渡されたすべてのパラメータをこのオブジェクトのプロパティに保存する必要があるため、コンストラクターでは渡されたすべてのパラメータをそのままコピーする必要があります。

エクスポートクラス Animation {
 コンストラクター(オブジェクト、プロパティ、開始値、終了値、期間、タイミング関数) {
 this.object = オブジェクト;
 this.property = プロパティ;
 this.startValue = 開始値;
 this.endValue = endValue;
 this.duration = 期間;
 this.timingFunction = タイミング関数;
 }
}

次に、アニメーションを実行する関数が必要です。 exec または go と呼ぶことができます。ここではrunという単語を使用します。個人的には、その機能の方が適切だと思います。

この関数は、仮想時間であるtimeパラメータを受け取る必要があります。リアルタイムを使用する場合は、タイムラインを作成する必要さえありません。

この時間を使用して、この時間に基づいて現在のアニメーション プロパティがどれだけ変化するかを計算できます。このプロパティの変化を計算するには、まずアニメーションの初期値から最終値までの合計変化範囲を知る必要があります。

数式:變化區間(range) = 終止值(endValue) - 初始值(startValue)

變換區間を取得したら、各フレームでアニメーションがどれだけ変化するかを計算できます。式は次のとおりです。

變化值= 變化區間值(range) * 時間(time) / 動畫時長(duration)

ここで取得した変更値は、現在の実行時間とアニメーションの合計期間に基づいてprogression (進行状況 %) を計算し、この進行状況のパーセンテージと変更範囲を使用して、初期値と現在の進行状況値の差を計算します。この差が變化值です。

この変更値は、CSS アニメーションのlinearアニメーション曲線に相当します。このアニメーション曲線は直線です。ここでは、 timingFunctionを処理せずに、これを使用して最初にAnimationクラスを実装します。この動的アニメーション カーブについては後で処理します。

この変更値を使用すると、startValue (初期値) + 変更値を使用して、現在の進行状況に対応する属性値を取得できます。コードの動作は次のようになります:

実行(時間) {
 範囲を this.endValue - this.startValue とします。
 this.object[this.property] = this.startValue + (範囲 * 時間) / this.duration;
}

この方法でアニメーションが機能します。次に、このアニメーションをタイムラインのアニメーション キューに追加して、キュー内で実行できるようにします。

上で述べたように、このアニメーションの run メソッドによって受信される時間は仮想時間です。したがって、Timeline で run メソッドを呼び出すときは、アニメーションが機能するように Animation に仮想時間を渡す必要があります。

さて、ここでタイムラインにアニメーションを追加したいのですが、まずアニメーション キューが必要です。このため、アニメーション セットを直接生成します。

これは他のタイムラインでの保存方法と同じです。保存するためにグローバル ANIMATIONS 定数を作成し、その値をシンボルにラップします。これにより、キューが誤って外部から呼び出されるのを防ぐことができます。

const ANIMATIONS = Symbol('animations');

Timeline クラスが構築されるときに、このキューに空のセットも割り当てられる必要があります。

コンストラクタ() {
 this[ANIMATIONS] = 新しい Set();
}

キューがある場合は、キューに参加するメソッドが必要なので、Timeline クラスにadd()メソッドも追加する必要があります。実装ロジックは次のとおりです。

コンストラクタ() {
 this[ANIMATIONS] = 新しい Set();
}

タイムラインでrunアニメーションに現在の実行期間を渡す必要があります。この期間を計算するには、タイムラインの先頭に開始時間を記録する必要があります。次に、アニメーションがトリガーされるたびに、當前時間- Timeline 開始時間を使用して、アニメーションの実行時間を取得します。

ただし、以前のtickconstructorに記述されており、Timeline の開始時間は start メソッドに配置する必要があります。そのため、この時間をより便利に取得するには、tick 宣言を start に直接配置します。

ただし、この変更により、タイムラインが開始されるたびにティック オブジェクト関数が再構築されることになります。ただし、この方法を使用すると、この機能をすばやく実装しやすくなりますが、より良いパフォーマンスを求める学生は、この領域を最適化することもできます。

ティックを移動した後、ANIMATIONS キューを呼び出すアニメーションをティックに追加できます。タイムラインには複数のアニメーションが存在する可能性があり、各フレームはそれらを次の進行状況属性状態にプッシュします。ここではループを使用して、ANIMATIONS キュー内のすべてのアニメーションの run メソッドを呼び出します。

最終的に、コードは次のようになります。

const TICK = シンボル('tick');
const TICK_HANDLER = Symbol('tick-handler');
const ANIMATIONS = Symbol('animations');

エクスポートクラスタイムライン{
 コンストラクタ() {
 this[ANIMATIONS] = 新しい Set();
 }
 始める() {
 startTime を Date.now() とします。
 this[TICK] = () => {
 t = Date.now() - startTime とします。
 for (this[ANIMATIONS]のアニメーションをlet) {
 アニメーションを実行します。
 }
 アニメーションフレームをリクエストします(this[TICK]);
 };
 this[TICK]();
 }
 一時停止() {}
 再開する() {}
 リセット() {}
 アニメーションを追加します。
 this[ANIMATIONS].add(アニメーション);
 }
}

エクスポートクラス Animation {
 コンストラクター(オブジェクト、プロパティ、開始値、終了値、期間、タイミング関数) {
 this.object = オブジェクト;
 this.property = プロパティ;
 this.startValue = 開始値;
 this.endValue = endValue;
 this.duration = 期間;
 this.timingFunction = タイミング関数;
 }

 実行(時間) {
 コンソールログ(時間);
 範囲を this.endValue - this.startValue とします。
 this.object[this.property] = this.startValue + (範囲 * 時間) / this.duration;
 }
}

デバッグを容易にするために、アニメーションの run メソッドにconsole.log(time)を追加します。

最後に、main.js でタイムラインにアニメーションを追加します。

'./framework.js' から Component、createElement をインポートします。
'./carousel.js' から { Carousel } をインポートします。
'./animation.js' から { Timeline, Animation } をインポートします。

ギャラリー = [
 'https://source.unsplash.com/Y8lCoTRgHPE/1600x900',
 'https://source.unsplash.com/v7daTKlZzaw/1600x900',
 'https://source.unsplash.com/DlkF4-dbCOU/1600x900',
 'https://source.unsplash.com/8SQ6xjkxkCo/1600x900',
];

a = <Carousel src={gallery} /> とします。

// document.body.appendChild(a);
ドキュメントの本文にマウントします。

tl = new Timeline();
// tl.add(新しいアニメーション({}, 'property', 0, 100, 1000, null));

tl.start(); 

アニメーションは実際に動作し、時間を取得できることがわかりました。しかし、アニメーションが止まらずに再生され続けるという問題も見つかりました。

次に、終了条件を追加する必要があります。現在の時刻がアニメーションの継続時間を超えている場合は、animation.run を実行する前に条件判断を行う必要があります。この時点でアニメーションを停止する必要があります。

まず、 start関数内のアニメーション ループ呼び出しを変更し、animation.run を実行する前に条件判断を追加する必要があります。ここでは、現在の時刻がアニメーションの継続時間よりも大きいかどうかを判断する必要があります。アニメーションが確立された場合は、アニメーションを停止し、アニメーションを ANIMATIONS キューから削除する必要があります。

エクスポートクラスタイムライン{
 コンストラクタ() {
 this[ANIMATIONS] = 新しい Set();
 }
 始める() {
 startTime を Date.now() とします。
 this[TICK] = () => {
 t = Date.now() - startTime とします。
 for (this[ANIMATIONS]のアニメーションをlet) {
 if (t > アニメーション.duration) {
  this[アニメーション].delete(アニメーション);
 }
 アニメーションを実行します。
 }
 アニメーションフレームをリクエストします(this[TICK]);
 };
 this[TICK]();
 }
 一時停止() {}
 再開する() {}
 リセット() {}
 アニメーションを追加します。
 this[ANIMATIONS].add(アニメーション);
 }
}

このように、複雑なロジックなしで停止条件を追加しました。最後に、main.js で Animation の最初のパラメータを変更します。渡されたオブジェクトにセッターを追加すると、アニメーションで時間を出力できるようになります。これによりデバッグが容易になります。

tl.add(
 新しいアニメーション(
 {
 設定a(a) {
 コンソールにログ出力します。
 },
 },
 '財産'、
 0,
 100,
 1000,
 ヌル
 )
); 

アニメーションは確かに停止していることがわかりますが、まだ問題が残っています。アニメーションの継続時間を 1000 ミリ秒に設定しましたが、最後の値は 1002 ミリ秒であり、明らかにアニメーションの継続時間を超えています。

したがって、アニメーションの終了条件に遭遇したときは、その期間 (アニメーション期間の値) をアニメーションに渡す必要があります。ここでは次のように記述します。

始める() {
 startTime を Date.now() とします。
 this[TICK] = () => {
 t = Date.now() - startTime とします。
 for (this[ANIMATIONS]のアニメーションをlet) {
 t0 = t とします。
 if (t > アニメーション.duration) {
  this[アニメーション].delete(アニメーション);
  t0 = アニメーションの継続時間;
 }
 アニメーションを実行します。
 }
 アニメーションフレームをリクエストします(this[TICK]);
 };
 this[TICK]();
 }
 一時停止() {}
 再開する() {}
 リセット() {}
 アニメーションを追加します。
 this[ANIMATIONS].add(アニメーション);
 }
} 

このようにして、タイムラインとアニメーションの予備機能が確立されます。


デザインタイムラインの更新

次に、このタイムラインにさらに多くの機能を追加して、アニメーション ライブラリを実際に使えるようにします。

CSS アニメーションでは、継続時間 (アニメーション継続時間) があることはわかっていますが、実際には遅延 (アニメーション遅延時間) もあります。

まず、この機能を追加してみます。

Delayプロパティのサポートを追加しました

開発中に、元のライブラリに機能を追加したい場合。私たちが最初に考慮するのは、「この機能を追加するのに適切な場所を見つけること」です。

実際、直感的に言えば、この関数はアニメーションの一部なので、この遅延を Animation クラスに配置するのが最初の本能です。しかし、より良いアイデアがあります。それは、タイムラインに遅延を入れることです。

アニメーションの開始時間、終了時間、および時間制御はすべてタイムライン関連の問題であり、実際にはアニメーションが重点を置いているものとは異なります。アニメーションに関しては、アニメーションの効果と操作に重点が置かれていると思います。

したがって、タイムラインに遅延を設定する方が明らかに適切です。

Timeline のadd()メソッドで、アニメーションをキューに追加するときに、遅延を追加します。

遅延ロジックを追加しながら、問題を解決することもできます。つまり、アニメーションをキューに追加すると、タイムラインがすでに実行されている可能性があります。このように、アニメーションを追加すると、アニメーションの開始時間が間違ってしまいます。

もう 1 つの問題は、start メソッドでは、 t開始時間とt0が必ずしも同じではないことです。 startTime は遅延に基づいて手動で定義できるためです。したがって、この値のロジックを書き直す必要があります。

さて、遅延機能を実装するときには、これら両方の要素を考慮に入れることができます。

まず、遅延パラメータを追加しましょう。

エクスポートクラス Animation {
 コンストラクター(オブジェクト、プロパティ、開始値、終了値、期間、遅延、タイミング関数) {
 this.object = オブジェクト;
 this.property = プロパティ;
 this.startValue = 開始値;
 this.endValue = endValue;
 this.duration = 期間;
 this.timingFunction = タイミング関数;
 this.delay = 遅延;
 }

 実行(時間) {
 コンソールログ(時間);
 範囲を this.endValue - this.startValue とします。
 this.object[this.property] = this.startValue + (範囲 * 時間) / this.duration;
 }
}

ここで行うことは、コンストラクターにdelayパラメーターを追加し、それをクラスの属性オブジェクトに格納することだけです。

タイムライン キューに追加された各アニメーションには異なる遅延がある可能性があるため、アニメーションの開始時間も異なります。したがって、すべてのアニメーションの開始時刻を保存するために、Timeline クラスのコンストラクターの下にSTART_TIMESストレージ スペースを作成する必要があります。

エクスポートクラス Animation {
 コンストラクター(オブジェクト、プロパティ、開始値、終了値、期間、遅延、タイミング関数) {
 this.object = オブジェクト;
 this.property = プロパティ;
 this.startValue = 開始値;
 this.endValue = endValue;
 this.duration = 期間;
 this.timingFunction = タイミング関数;
 this.delay = 遅延;
 }

 実行(時間) {
 コンソールログ(時間);
 範囲を this.endValue - this.startValue とします。
 this.object[this.property] = this.startValue + (範囲 * 時間) / this.duration;
 }
}

次に、タイムラインにアニメーションを追加する add メソッドで、アニメーションの開始時間を START_TIMES データに追加します。ユーザーが add メソッドに startTime パラメータを渡さない場合は、デフォルト値Date.now()を指定する必要があります。

アニメーション、開始時間を追加します。
 引数の長さが 2 未満の場合は、startTime を Date.now() に設定します。
 this[ANIMATIONS].add(アニメーション);
 this[START_TIMES].set(アニメーション、開始時間);
}

次に、開始時間のロジックを変換します。

  • 最初のケース:アニメーションの開始時間がタイムラインの開始時間よりも短い場合、現在のアニメーションの時間の進行は當前時間- Timeline 開始時間
  • 2番目のケース:アニメーションの開始時間がタイムラインの開始時間よりも大きい場合、現在のアニメーションの時間進行は當前時間- 動畫的開始時間になります。

コードは次のように実装されます。

始める() {
 startTime を Date.now() とします。
 this[TICK] = () => {
 now = Date.now() とします。
 for (this[ANIMATIONS]のアニメーションをlet) {
 t とします。

 if (this[START_TIMES].get(アニメーション) < 開始時間) {
 t = 現在 - 開始時刻;
 } それ以外 {
 t = 現在 - this[START_TIMES].get(アニメーション);
 }

 t > アニメーションの継続時間の場合
 this[アニメーション].delete(アニメーション);
 t = アニメーションの継続時間;
 }
 アニメーションを実行します。
 }
 アニメーションフレームをリクエストします(this[TICK]);
 };
 this[TICK]();
}

このように、Timline ではいつでもアニメーションを追加できます。この新しい機能のテストを容易にするために、 tlanimation両方をwindowにマウントします。

ここでmain.jsのコードを変更します:

始める() {
 startTime を Date.now() とします。
 this[TICK] = () => {
 now = Date.now() とします。
 for (this[ANIMATIONS]のアニメーションをlet) {
 t とします。

 if (this[START_TIMES].get(アニメーション) < 開始時間) {
 t = 現在 - 開始時刻;
 } それ以外 {
 t = 現在 - this[START_TIMES].get(アニメーション);
 }

 if (t > アニメーション.duration) {
 this[アニメーション].delete(アニメーション);
 t = アニメーションの継続時間;
 }
 アニメーションを実行します。
 }
 アニメーションフレームをリクエストします(this[TICK]);
 };
 this[TICK]();
}

webpack で再パッケージ化した後、コンソールで次のコマンドを実行して、タイムラインにアニメーションを追加できます。

tl.add(アニメーション); 

さて、これがタイムラインの更新されたデザインです。しかし、この時点では、アニメーションを遅延させるための delay パラメータの値は実際には設定されていません。

実際、ここで必要なのは、 tを計算し、そこからanimation.delayを減算することだけです。

if (this[START_TIMES].get(アニメーション) < 開始時間) {
 t = 現在 - 開始時間 - アニメーションの遅延;
} それ以外 {
 t = 現在 - this[START_TIMES].get(animation) - animation.delay;
}

ただし、特別なケースに注意する必要があります。t t - 延遲時間で得られた時間が 0 未満の場合、アニメーションが実行に必要な時間に達していないことを意味します。アニメーションは、t > 0 の場合にのみ実行する必要があります。最後に、アニメーションを実行するロジックに判断を追加します。

t > 0 の場合、アニメーションを実行します。

次に、一時停止と再開の機能を実装してみましょう。


一時停止と再開機能の実装

まずは一時停止機能を追加してみます。

一時停止の実装

タイムラインの一時停止機能を実装するには、まずティックをキャンセルする必要があります。つまり、私たちのタイムラインの時間は止まります。時計の秒針が止まれば、当然時間も止まります。

ティックをキャンセルするには、まずティックがトリガーされたときに何が起こっているかを知る必要があります。言うまでもなく、これはrequestAnimationFrameです。

最初に宣言したTICK_HANDLERを覚えていますか?この定数は、現在のティック イベントを保存するために使用されます。

したがって、最初のステップは、TICK_HANDLER を使用して requestAnimationFrame を保存することです。ティックは Timeline クラスの start メソッドで開始されるため、ここでは start メソッドのrequestAnimationFrame変更する必要があります。

始める() {
startTime を Date.now() とします。
 this[TICK] = () => {
 now = Date.now() とします。
 for (this[ANIMATIONS]のアニメーションをlet) {
 t とします。

 if (this[START_TIMES].get(アニメーション) < 開始時間) {
 t = 現在 - 開始時間 - アニメーションの遅延;
 } それ以外 {
 t = 現在 - this[START_TIMES].get(animation) - animation.delay;
 }

 if (t > アニメーション.duration) {
 this[アニメーション].delete(アニメーション);
 t = アニメーションの継続時間;
 }
 t > 0 の場合、アニメーションを実行します。
 }
 this[TICK_HANDLER] = requestAnimationFrame(this[TICK]);
 };
 this[TICK]();
}

次に、 pause()メソッドで次のcancelAnimationFrame呼び出します。

一時停止() {
 アニメーションフレームをキャンセルします(this[TICK_HANDLER]);
}

一時停止は比較的簡単ですが、再開はより複雑です。

履歴書の実装

次に、再開を実装するための最初のステップは、ティックを再起動することです。しかし、tick の t (アニメーション開始時間) は明らかに間違っているので、一時停止のロジックを処理する方法を見つける必要があります。

Resume を実装する前に、DOM をいくつか操作してテストする必要があります。まず新しい HTML を作成し、その中にdiv要素を作成します。

<!-- 新しい animation.html を作成します (dist フォルダーに配置します) -->

<スタイル>
。箱 {
 幅: 100ピクセル;
 高さ: 100px;
 背景色: 水色;
}
</スタイル>

<本文>
 <div class="box"></div>
 <script src="./main.js"></script>
</本文>

そうすると、 main.js必要なくなるので、アニメーション呼び出しを実装するために別のanimation-demo.jsを作成します。こうすれば、カルーセルを操作する必要がなくなります。

// ルートディレクトリに `animation-demo.js` を作成します
'./animation.js' から { Timeline, Animation } をインポートします。

tl = new Timeline();

tl.start();
tl.add(
 新しいアニメーション(
 {
 設定a(a) {
 コンソールにログ出力します。
 },
 },
 '財産'、
 0,
 100,
 1000,
 ヌル
 )
);

ページで使用されている js エントリ ファイルを変更したためです。そこで、 webpack.config.jsに移動して、エントリをanimation-demo.jsに変更する必要があります。

モジュール.エクスポート = {
 エントリ: './animation-demo.js',
 モード: '開発'、
 開発サーバー: {
 コンテンツベース: './dist',
 },
 モジュール: {
 ルール:
 {
 テスト: /\.js$/,
 使用: {
  ローダー: 'babel-loader',
  オプション:
  プリセット: ['@babel/preset-env'],
  プラグイン: [['@babel/plugin-transform-react-jsx', { プラグマ: 'createElement' }]],
  },
 },
 },
 ]、
 },
}; 

現在、JavaScript はシミュレートされたアニメーション出力です。次に、アニメーションに要素を操作する機能を追加してみます。

まず、スクリプト内でこの要素を取得しやすくするために、要素にid="el"を追加します。

<div class="box" id="el"></div>

次に、このプロトタイプをアニメーション化します。まず、 animation-demo.jsに戻り、Animation インスタンス化の最初のパラメータdocument.querySelector('#el').styleに変更する必要があります。

次に、2 番目のパラメータのプロパティが"transform"に変更されます。ただし、次の開始時間と終了時間は transform 属性に使用できないことに注意してください。

したがって、変換templateが必要であり、このテンプレートを使用して時間を transform の対応する値に変換します。

ここでのテンプレート値は関数として直接記述されます。

 v => `translate(${$v}px)`;

最終的に、コードは次のようになります。

tl.add(
 新しいアニメーション(
 document.querySelector('#el').style,
 '変身'、
 0,
 100,
 1000,
 0,
 ヌル、
 v => `translate(${v}px)`
 )
);

この部分を調整した後、animation.js に移動して対応する調整を行う必要があります。

最初のステップは、Animation クラスのコンストラクターにテンプレート パラメーターを追加することです。他のプロパティと同様に、これはコンストラクター内の単なるストレージ操作です。

次に、アニメーションの run メソッドで、 this.object[this.property]の値がテンプレート メソッドを呼び出してプロパティ値を生成する必要があります。以前のように特定の属性に直接割り当てるのではなく。

エクスポートクラス Animation {
 コンストラクタ(
 物体、 
 財産、
 開始値、
 終了値、
 間隔、
 遅れ、
 タイミング関数、
 テンプレート
 ){
 this.object = オブジェクト;
 this.property = プロパティ;
 this.startValue = 開始値;
 this.endValue = endValue;
 this.duration = 期間;
 this.timingFunction = タイミング関数;
 this.delay = 遅延;
 this.template = テンプレート;
 }

 実行(時間) {
 範囲を this.endValue - this.startValue とします。
 this.object[this.property] = 
 このテンプレート(
 this.startValue + (範囲 * 時間) / this.duration
 );
 }
}

最終的な効果は次のようになります。

要素のアニメーションを制御するために、すでにアニメーション ライブラリを使用できることがわかりました。

まず、これらのアニメーションのパラメータを調整し、開始位置と終了位置を 0 ~ 500 に変更し、アニメーションの継続時間を 2000 ミリ秒に変更します。この設定は、次の機能のデバッグに役立ちます。

tl.add(
 新しいアニメーション(
 document.querySelector('#el').style,
 '変身'、
 0,
 500,
 2000年、
 0,
 ヌル、
 v => `translate(${v}px)`
 )
);

さて、次に一時停止ボタンを追加しましょう。

<本文>
 <div class="box" id="el"></div>
 <button id="pause-btn">一時停止</button>
 <script src="./main.js"></script>
</本文>

次に、animation-demo.js に戻ってこの要素をバインドします。そして、タイムラインで一時停止メソッドを実行させます。

document.querySelector('#pause-btn').addEventListener(
 'クリック'、
 () => tl.pause()
); 

一時停止関数がOKであることがわかりますが、このアニメーションをどのように再生する必要がありますか?つまり、履歴書の機能を実装することです。

この履歴書関数のロジックを実装する前に、まず同じ方法で履歴書ボタンを作成します。このボタンをタイムラインのresume()メソッドを呼び出します。

<! -  animation.html->

<本文>
 <div class = "box" id = "el"> </div>
 <button id = "Pause-btn"> Pause </button>
 <button id = "resume-btn"> resume </button>
 <script src="./main.js"></script>
</本文>
// resumeボタンのイベントバインディングをAnimation-demo.jsに追加します。

document.queryselector( '#resume-btn')。addeventlistener(
 'クリック'、
 ()=> tl.resume()
);

上記の論理によれば、履歴書の最も基本的な理解は、ダニを再開することです。次に、履歴書方法でthis[TICK]()直接実行してみましょう。

再開する() {
 この[ティック]();
} 

アニメーションでは、履歴書でティックを直接実行すると、アニメーションを再起動するボックスが元の一時停止位置でアニメーションを再生し続けないことがわかります。代わりに、彼は後ろにジャンプしました。

明らかに、履歴書をクリックしたとき、私たちのアニメーションは、私たちが一時停止したときにどこにいたかを覚えていませんでした。したがって、アニメーションを一時停止している間、開始時間を記録し、暫停的開始時間暫停時間必要があります。

これらの2つの変数はアニメーションクラスで使用する必要があるため、ここでグローバルな範囲で定義する必要があります。次に、2つの定数PAUSE_STARTPAUSE_TIMEを使用して保存します。

const pause_start = symbol( 'pause-start');
const pause_time = symbol( 'Pause-Time');

次に、一時停止する時間を記録します。

一時停止() {
 この[pause_start] = date.now();
 CancelAnimationFrame(this [tick_handler]);
}

実際、一時停止の開始時間を記録するのはなぜですか?アニメーションをいつ再生し続けるかを知るのはどれくらいの期間かを知ることです。

アニメーションで見たばかりの現象は何ですか?ティックを再起動するときであり、アニメーションの開始時間は現在の時間を使用します。ここで言及されている「現在の」時間は、タイムラインがすでになくなっている場所です。明らかに、この開始時間が正しくありません。

一時停止している瞬間の時間を記録する場合。次に、履歴書をクリックしたときにクリックする時間から持続時間を計算します。このようにしてt(動畫開始時間)- 暫停時長= 當前動畫應該繼續播放的時間

このアルゴリズムを使用して、アニメーションを元の一時停止位置で再生し続けることができます。

次に、コードロジックの実装方法を見てみましょう。

次に、時間録画の論理に一時停止を追加しました。一時停止時間を記録する前に、この値0に初期値を割り当てる場所が必要です。

最良のことは、タイムラインの開始時にこのデフォルト値を与えることです。 Pause_timeの初期値の後、履歴書を実行すると、 Date.now() - PAUSE_START使用して、Pauseアニメーションの合計期間を現在に取得できます。

ここには、注意を払う必要があるポイントがあります。私たちのアニメーションには、複数の一時停止と複数の再控除がある場合があります。したがって、この場合、この式を毎回使用して新しい一時停止期間を計算し、Pause_time値を上書きすると、実際には間違っています。

タイムラインがオンになるとタイムラインが停止しないため、時間は常に経過しています。毎回現在の一時停止時間を計算するだけで、フォールバック時間は実際に間違っています。正しい方法は、一時停止するたびに一時停止の期間を追加することです。これが最後の時間です。

そのため、Pause_Timeに値を割り当てるとき、割り当てをオーバーライドする代わりに+=を使用します。

最後に、私たちが変換したタイムラインは次のようなものです。

エクスポートクラスのタイムライン{
 コンストラクタ() {
 この[animations] = new set();
 この[start_times] = new Map();
 }
 始める() {
 startTime を Date.now() とします。
 この[pause_time] = 0;
 この[tick] =()=> {
 now = Date.now() とします。
 for(この[アニメーション]のアニメーションをレット){
 tをしましょう。

 if(this [start_times] .get(animation)<starttime){
  t = now -starttime -animation.delay -this [pause_time];
 } それ以外 {
  t = now -this [start_times] .get(animation) -  animation.delay -this [pause_time];
 }

 if(t> animation.duration){
  この[アニメーション] .delete(アニメーション);
  t = animation.duration;
 }
 if(t> 0)animation.run(t);
 }
 この[tick_handler] = requestAnimationFrame(this [tick]);
 };
 この[ティック]();
 }
 一時停止() {
 この[pause_start] = date.now();
 CancelAnimationFrame(this [tick_handler]);
 }
 再開する() {
 この[pause_time] += date.now() -  this [pause_start];
 この[ティック]();
 }
 reset(){}
 add(animation、starttime){
 if(arguments.length <2)starttime = date.now();
 この[アニメーション] .Add(アニメーション);
 この[start_times] .set(animation、starttime);
 }
}

コードを実行して、それが正しいかどうかを確認しましょう。

このようにして、一時停止と履歴書の関数を完了しました。

ここでは、利用可能なタイムラインのタイムラインを実装します。

あなたが開発者である場合、個人的なブログを作成することも履歴書のハイライトです。そして、あなたが超クールなブログを持っているなら、それはさらに明るくなり、光沢があります。

トピックGithubアドレス:https://github.com/auroral-ui/hexo-theme-aurora
トピックの使用文書:https://aurora.tridiamond.tech/zh/


これは、タイムラインとアニメーション効果(フロントエンドのコンポーネント化)を実装するための記事です。

以下もご興味があるかもしれません:
  • d3.jsスケーラブルなタイムライントポロジ図を実装するためのコードの例
  • AngularJのタイムライン効果を実装するためのサンプルコード
  • JSは、タイムラインの自動配置を実現します
  • Timergliderjs jQueryベースのタイムラインプラグイン

<<:  Dockerでnginxを実行し、ローカルディレクトリをイメージにマウントする方法

>>:  Win7x64でのMySQL 5.7.18解凍版のインストール方法

推薦する

ubuntu16.04 で nginx を完全にアンインストールするための関連コマンド

nginx の概要nginx は、無料のオープンソースの高性能 HTTP サーバーおよびリバース プ...

Centos7 システムでの MySQL マスター スレーブ同期構成スキーム

序文最近、高可用性プロジェクトに取り組む際には、データの同期が必要になっています。ノードが 2 つし...

フロントエンドの vue+express ファイルのアップロードとダウンロードの例

新しいserver.jsを作成する糸初期化 -y 糸を追加エクスプレスノードモン -D var ex...

Vue3+el-tableは行と列の変換を実現します

目次行と列の変換トランスクリプトの構成を分析するvue3 + el-table で作成されたトランス...

XHTML+CSS Web ページ作成における美しいスタイルシートの適用

これはかなり前に書かれた記事です。今となっては、その中の考え方は学ぶ価値があるように思えます。jb5...

PC/Pad/Phoneデバイスに自動的に適応するCSSウェブページレスポンシブレイアウト

序文最近は、PC、iPad、携帯電話、スマートウォッチ、スマートテレビなど、さまざまなデバイスが存在...

Javascript の奇妙な点をご存知ですか?

私たちのベテランの先人たちは、数え切れないほどのコードを書き、数え切れないほどの落とし穴に陥ってきま...

src 属性と href 属性の違い

src と href には違いがあり、混同される可能性があります。 src は現在の要素を置き換える...

Vueはカウントダウン機能を実装する

この記事の例では、カウントダウン機能を実装するためのVueの具体的なコードを参考までに共有しています...

スタックメニューを実装するためのjQueryプラグイン

jQueryプラグインの毎日の積み重ねメニュー、参考までに、具体的な内容は次のとおりです。スタックメ...

ico ミラー コードを HTML に追加します (favicon.ico はルート ディレクトリに配置されます)

コード:コードをコピーコードは次のとおりです。 <!DOCTYPE html PUBLIC &...

JavaScript でシンプルなクリスマス ゲームを実装する

目次序文成果を達成するコードCSSコードJSコードHTMLコードデモンストレーションのプロセス序文ク...

Docker イメージ + nginx を使用して Vue プロジェクトをデプロイする方法

1. Vueプロジェクトのパッケージ化開発されたvueプロジェクトに次の名前を入力し、パッケージ化し...

MySQL の全体的なアーキテクチャの紹介

MySQL の全体的なアーキテクチャは、サーバー層とストレージ エンジン層に分かれています。サーバー...

MySQL バイナリログデータ復旧: 誤ってデータベースを削除した場合の詳細な説明

MySQL Binログデータの回復: 誤ってデータベースを削除した場合前書き: テスト マシンで誤っ...