- 智能计数器
- 原生 JS
- HTML
- 原生 JS
- Angular 版本
- HTML
- 使用到的操作符
智能计数器
一个在页面上带有动态更新数字效果的有趣元素就是智能计数器,也可以称之为里程表效果。不采用上下跳数的方式,而是快速地清点到期望的数字,这能达到一种很酷的效果。能做到这点的流行库的其中一个就是由 Hubspot 所写的 odometer 。让我们来看看如何使用短短几行 RxJS 代码来实现类似的效果。

原生 JS
( JSBin |
JSFiddle )
// 工具函数const takeUntilFunc = (endRange, currentNumber) => {return endRange > currentNumber? val => val <= endRange: val => val >= endRange;};const positiveOrNegative = (endRange, currentNumber) => {return endRange > currentNumber ? 1 : -1;};const updateHTML = id => val => (document.getElementById(id).innerHTML = val);// 显示const input = document.getElementById('range');const updateButton = document.getElementById('update');const subscription = (function(currentNumber) {return fromEvent(updateButton, 'click').pipe(map(_ => parseInt(input.value)),switchMap(endRange => {return timer(0, 20).pipe(mapTo(positiveOrNegative(endRange, currentNumber)),startWith(currentNumber),scan((acc, curr) => acc + curr),takeWhile(takeUntilFunc(endRange, currentNumber));)}),tap(v => (currentNumber = v)),startWith(currentNumber)).subscribe(updateHTML('display'));})(0);
HTML
<input id="range" type="number"><button id="update">Update</button><h3 id="display">0</h3>
我们可以轻易地获取我们的原生 JS 所写的智能计数器并将其包装在任何流行的基于 UI 库中。下面是 Angular 版本的智能计数器,它接收一个更新结束范围的 Input 输入属性并执行适当的转换。
Angular 版本
(
StackBlitz
)
import { Component, Input, OnDestroy } from '@angular/core';import { Subject } from 'rxjs/Subject';import { timer } from 'rxjs/observable/timer';import { switchMap, startWith, scan, takeWhile, takeUntil, mapTo } from 'rxjs/operators';@Component({selector: 'number-tracker',template: `<h3> {{ currentNumber }}</h3>`})export class NumberTrackerComponent implements OnDestroy {@Input()set end(endRange: number) {this._counterSub$.next(endRange);}@Input() countInterval = 20;public currentNumber = 0;private _counterSub$ = new Subject();private _onDestroy$ = new Subject();constructor() {this._counterSub$.pipe(switchMap(endRange => {return timer(0, this.countInterval).pipe(mapTo(this.positiveOrNegative(endRange, this.currentNumber)),startWith(this.currentNumber),scan((acc: number, curr: number) => acc + curr),takeWhile(this.isApproachingRange(endRange, this.currentNumber)))}),takeUntil(this._onDestroy$)).subscribe((val: number) => this.currentNumber = val);}private positiveOrNegative(endRange, currentNumber) {return endRange > currentNumber ? 1 : -1;}private isApproachingRange(endRange, currentNumber) {return endRange > currentNumber? val => val <= endRange: val => val >= endRange;}ngOnDestroy() {this._onDestroy$.next();this._onDestroy$.complete();}}
HTML
<p><input type="number"(keyup.enter)="counterNumber = vanillaInput.value"#vanillaInput><button(click)="counterNumber = vanillaInput.value">Update number</button></p><number-tracker [end]="counterNumber"></number-tracker>
使用到的操作符
- fromEvent
- map
- mapTo
- scan
- startWith
- switchMap
- takeWhile
