角度とRXJSでカウントアップアニメーションを構築すること
28287 ワード
カバー写真Andy Holmes を返します.
この記事では、カウントアップアニメーションを無効にする方法を説明します.サードパーティ製のライブラリを使わずにゼロからディレクティブディレクティブを構築します.最終的な結果は次のようになります.
始めましょう!
角度CLIによる指令の生成
角度でディレクティブを作成するには、次のコマンドを実行します
入力の定義
The
ローカル州の定義
The
計算カウント
現在のカウントを計算するには、
表示カウント
ディレクティブのホスト要素内で現在のカウントをレンダリングするには、
それから、我々はそれを聞くメソッドを作成する必要があります
包む
の最終バージョン
Demo
資源 Official docs of the Official docs of the
ピアレビュー
ティムにこの記事に役立つ提案を与えてくれてありがとう!
この記事では、カウントアップアニメーションを無効にする方法を説明します.サードパーティ製のライブラリを使わずにゼロからディレクティブディレクティブを構築します.最終的な結果は次のようになります.
始めましょう!
角度CLIによる指令の生成
角度でディレクティブを作成するには、次のコマンドを実行します
ng generate directive count-up
角度CLIはcount-up.directive.ts
空のディレクティブを含むファイル:@Directive({
selector: '[countUp]'
})
export class CountUpDirective {
constructor() {}
}
入力の定義
The
CountUpDirective
つの入力を持ちます:カウントとアニメーション持続時間、カウント入力の名前が指令セレクタの名前と同じであるところ.使用CountUpDirective
テンプレートで次のようになります.<p [countUp]="200" [duration]="5000"></p>
これらの入力はCountUpDirective
次のようになります.@Directive({
selector: '[countUp]'
})
export class CountUpDirective {
@Input('countUp') // input name is the same as selector name
set count(count: number) {}
@Input()
set duration(duration: number) {}
}
ご覧のように、入力はセッターとして定義されます.入力値はRXJS被験者に放出されます.そして、それは我々がOnChanges
ライフサイクルフック.ローカル州の定義
The
CountUpDirective
動作対象に格納される2つのローカルステートスライスがあります.@Directive({
selector: '[countUp]'
})
export class CountUpDirective {
// default count value is 0
private readonly count$ = new BehaviorSubject(0);
// default duration value is 2000 ms
private readonly duration$ = new BehaviorSubject(2000);
}
次に、入力値が変更されると、これらの対象に新しい入力値が出力されます@Directive({
selector: '[countUp]'
})
export class CountUpDirective {
private readonly count$ = new BehaviorSubject(0);
private readonly duration$ = new BehaviorSubject(2000);
@Input('countUp')
set count(count: number) {
// emit a new value to the `count$` subject
this.count$.next(count);
}
@Input()
set duration(duration: number) {
// emit a new value to the `duration$` subject
this.duration$.next(duration);
}
}
次のステップは、ビルドすることですcurrentCount$
現在のカウントをテンプレートで表示するために使用可能です.計算カウント
現在のカウントを計算するには、
count$
and duration$
科目私たちはcombineLatest
演算子は、現在のカウントの計算をリセットするたびにcount$
or duration$
変更点次のステップは、0で始まる間隔で外側の観測可能な状態を切り換えることです.そして、時間をかけて現在のカウントを増やしますcount
アニメーションの持続時間が満了する値private readonly currentCount$ = combineLatest([
this.count$,
this.duration$,
]).pipe(
switchMap(([count, duration]) => {
// get the time when animation is triggered
const startTime = animationFrameScheduler.now();
// use `animationFrameScheduler` for better rendering performance
return interval(0, animationFrameScheduler).pipe(
// calculate elapsed time
map(() => animationFrameScheduler.now() - startTime),
// calculate progress
map((elapsedTime) => elapsedTime / duration),
// complete when progress is greater than 1
takeWhile((progress) => progress <= 1),
// apply quadratic ease-out function
// for faster start and slower end of counting
map((progress) => progress * (2 - progress)),
// calculate current count
map((progress) => Math.round(progress * count)),
// make sure that last emitted value is count
endWith(count),
distinctUntilChanged()
);
}),
);
使用するanimationFrameScheduler
デフォルトの代わりにasyncScheduler
より良いレンダリングパフォーマンス.時animationFrameScheduler
を使用するinterval
, 最初の引数は0
. そうでなければ、それはasyncScheduler
. 言い換えれば、以下の実装currentCount$
用途asyncScheduler
フードの下でanimationFrameScheduler
はinterval
機能private readonly currentCount$ = combineLatest([
this.count$,
this.duration$,
]).pipe(
switchMap(([count, animationDuration]) => {
const frameDuration = 1000 / 60; // 60 frames per second
const totalFrames = Math.round(animationDuration / frameDuration);
// interval falls back to `asyncScheduler`
// because the `frameDuration` is different from 0
return interval(frameDuration, animationFrameScheduler).pipe(
// calculate progress
map((currentFrame) => currentFrame / totalFrames),
// complete when progress is greater than 1
takeWhile((progress) => progress <= 1),
// apply quadratic ease-out function
map((progress) => progress * (2 - progress)),
// calculate current count
map((progress) => Math.round(progress * count)),
// make sure that last emitted value is count
endWith(count),
distinctUntilChanged()
);
})
);
💡 If you're not familiar with the
animationFrameScheduler
and its advantages for updating the DOM over theasyncScheduler
, take a look at the resources section.
表示カウント
ディレクティブのホスト要素内で現在のカウントをレンダリングするには、
Renderer2
とホスト要素への参照.両方ともコンストラクタを通して注入できます.また、私たちはDestroy
私たちを助けるために役立つプロバイダcurrentCount$
観測可能なCountUpDirective
破棄されます:@Directive({
selector: '[countUp]',
// `Destroy` is provided at the directive level
providers: [Destroy],
})
export class CountUpDirective {
constructor(
private readonly elementRef: ElementRef,
private readonly renderer: Renderer2,
private readonly destroy$: Destroy
) {}
}
💡 Take a look at to learn more about
Destroy
provider.
それから、我々はそれを聞くメソッドを作成する必要があります
currentCount$
ホスト要素内の値の変更と表示private displayCurrentCount(): void {
this.currentCount$
.pipe(takeUntil(this.destroy$))
.subscribe((currentCount) => {
this.renderer.setProperty(
this.elementRef.nativeElement,
'innerHTML',
currentCount
);
});
}
The displayCurrentCount
メソッドはngOnInit
メソッド.包む
の最終バージョン
CountUpDirective
以下のようになります./**
* Quadratic Ease-Out Function: f(x) = x * (2 - x)
*/
const easeOutQuad = (x: number): number => x * (2 - x);
@Directive({
selector: '[countUp]',
providers: [Destroy],
})
export class CountUpDirective implements OnInit {
private readonly count$ = new BehaviorSubject(0);
private readonly duration$ = new BehaviorSubject(2000);
private readonly currentCount$ = combineLatest([
this.count$,
this.duration$,
]).pipe(
switchMap(([count, duration]) => {
// get the time when animation is triggered
const startTime = animationFrameScheduler.now();
return interval(0, animationFrameScheduler).pipe(
// calculate elapsed time
map(() => animationFrameScheduler.now() - startTime),
// calculate progress
map((elapsedTime) => elapsedTime / duration),
// complete when progress is greater than 1
takeWhile((progress) => progress <= 1),
// apply quadratic ease-out function
// for faster start and slower end of counting
map(easeOutQuad),
// calculate current count
map((progress) => Math.round(progress * count)),
// make sure that last emitted value is count
endWith(count),
distinctUntilChanged()
);
}),
);
@Input('countUp')
set count(count: number) {
this.count$.next(count);
}
@Input()
set duration(duration: number) {
this.duration$.next(duration);
}
constructor(
private readonly elementRef: ElementRef,
private readonly renderer: Renderer2,
private readonly destroy$: Destroy
)
ngOnInit(): void {
this.displayCurrentCount();
}
private displayCurrentCount(): void {
this.currentCount$
.pipe(takeUntil(this.destroy$))
.subscribe((currentCount) => {
this.renderer.setProperty(
this.elementRef.nativeElement,
'innerHTML',
currentCount
);
});
}
}
Demo
資源
requestAnimationFrame
function animationFrameScheduler
ピアレビュー
Reference
この問題について(角度とRXJSでカウントアップアニメーションを構築すること), 我々は、より多くの情報をここで見つけました https://dev.to/angular/building-count-up-animation-with-angular-and-rxjs-240kテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol