ホームページ >ウェブフロントエンド >jsチュートリアル >ディレクティブを使用した Angular の Figma のような入力フィールド
Figma に詳しい人は、入力フィールドがドラッグによる値の増減をサポートしていることに気づいたでしょう。最初に入力フィールドをクリックしてから数値を入力する代わりに、ドラッグすることで目的の値を簡単に取得できるため、ドラッグ機能は非常に便利です。
Angular ディレクティブを使用してそのようなものを構築できます。この実験では、Angular の最新機能をすべて使用します。
これをどのように構築できるかを見てみましょう。
実際には、これを複数の方法で行うことができます。ディレクティブを使用してこれを構築します。私たちがこれを行う方法は、非常に一般的なアプローチをとることです。こうすることで、要素やサイドバーのサイズ変更などのためにロジックを再利用できます。
入力のメインロジックを抽出してディレクティブにカプセル化できます。主な目的は、マウス イベントをリッスンし、マウスの動きを使用可能な値に変換することです。もう少し詳しく説明すると、
ユーザーがマウスをクリックしたとき (マウスダウン イベント)。
マウスの動き (mousemove イベント) の監視を開始し、その情報を使用して使用可能な値に変換します。
ユーザーがクリックを放すと、リスナー (マウスアップ イベント) が停止します。
rxjs を使用してロジックを少し単純化します。
疑似コードは次のようになります。
const mousedown$ = fromEvent<MouseEvent>(target, 'mousedown'); const mousemove$ = fromEvent<MouseEvent>(document, 'mousemove'); const mouseup$ = fromEvent<MouseEvent>(document, 'mouseup'); let startX = 0; let step = 1; mousedown$ .pipe( tap((event) => { startX = event.clientX; // Initial x co-ordinate where the mouse down happened }), switchMap(() => mousemove$.pipe(takeUntil(mouseup$)))) .subscribe((moveEvent) => { const delta = startX - moveEvent.clientX; const newValue = Math.round(startValueAtTheTimeOfDrag + delta); });
上記のコードを見ると、何が起こっているのかがかなり明確になるはずです。基本的に、X 軸上のクリックの位置である clientX の初期値を保存します。この情報を取得すると、ユーザーがマウスを移動したときに、最初の開始位置と現在の X 位置からデルタを計算できます。
次のようなカスタマイズをさらに追加できます。
感度 - 最終値までのドラッグ距離は感度によって決まります。感度の値が高いほど、動きがそれほど大きくなくても最終的な値が大きくなります。
ステップ - マウスを移動するときのステップ間隔を設定します。ステップ値が 1 の場合、最終値は 1 ずつ増加/減少します。
Min - 出力される最小値。
Max - 出力される最大値。
最終的なディレクティブは次のようになります:
@Directive({ selector: "[scrubber]", }) export class ScrubberDirective { public readonly scrubberTarget = input.required<HTMLDivElement>({ alias: "scrubber", }); public readonly step = model<number>(1); public readonly min = model<number>(0); public readonly max = model<number>(100); public readonly startValue = model(0); public readonly sensitivity = model(0.1); public readonly scrubbing = output<number>(); private isDragging = signal(false); private startX = signal(0); private readonly startValueAtTheTimeOfDrag = signal(0); private readonly destroyRef = inject(DestroyRef); private subs?: Subscription; constructor() { effect(() => { this.subs?.unsubscribe(); this.subs = this.setupMouseEventListener(this.scrubberTarget()); }); this.destroyRef.onDestroy(() => { document.body.classList.remove('resizing'); this.subs?.unsubscribe(); }); } private setupMouseEventListener(target: HTMLDivElement): Subscription { const mousedown$ = fromEvent<MouseEvent>(target, "mousedown"); const mousemove$ = fromEvent<MouseEvent>(document, "mousemove"); const mouseup$ = fromEvent<MouseEvent>(document, "mouseup"); return mousedown$ .pipe( tap((event) => { this.isDragging.set(true); this.startX.set(event.clientX); this.startValueAtTheTimeOfDrag.set(this.startValue()); document.body.classList.add("resizing"); }), switchMap(() => mousemove$.pipe( takeUntil( mouseup$.pipe( tap(() => { this.isDragging.set(false); document.body.classList.remove("resizing"); }) ) ) ) ) ) .subscribe((moveEvent) => { const delta = moveEvent.clientX - this.startX(); const deltaWithSensitivityCompensation = delta * this.sensitivity(); const newValue = Math.round( (this.startValueAtTheTimeOfDrag() + deltaWithSensitivityCompensation) / this.step() ) * this.step(); this.emitChange(newValue); this.startValue.set(newValue); }); } private emitChange(newValue: number): void { const clampedValue = Math.min(Math.max(newValue, this.min()), this.max()); this.scrubbing.emit(clampedValue); } }
ディレクティブの準備ができたので、実際にそれを使用する方法を見てみましょう。
const mousedown$ = fromEvent<MouseEvent>(target, 'mousedown'); const mousemove$ = fromEvent<MouseEvent>(document, 'mousemove'); const mouseup$ = fromEvent<MouseEvent>(document, 'mouseup'); let startX = 0; let step = 1; mousedown$ .pipe( tap((event) => { startX = event.clientX; // Initial x co-ordinate where the mouse down happened }), switchMap(() => mousemove$.pipe(takeUntil(mouseup$)))) .subscribe((moveEvent) => { const delta = startX - moveEvent.clientX; const newValue = Math.round(startValueAtTheTimeOfDrag + delta); });
現在、scrubberTarget 入力を input.required としてマークしていますが、実際にはこれをオプションにして、ディレクティブのホストの elementRef.nativeElement を自動的に使用することもでき、同様に機能します。別の要素をターゲットとして設定する場合に備えて、scrubberTarget が入力として公開されます。
サイズ変更カーソルを正しく設定できるように、本体にサイズ変更クラスも追加します。
@Directive({ selector: "[scrubber]", }) export class ScrubberDirective { public readonly scrubberTarget = input.required<HTMLDivElement>({ alias: "scrubber", }); public readonly step = model<number>(1); public readonly min = model<number>(0); public readonly max = model<number>(100); public readonly startValue = model(0); public readonly sensitivity = model(0.1); public readonly scrubbing = output<number>(); private isDragging = signal(false); private startX = signal(0); private readonly startValueAtTheTimeOfDrag = signal(0); private readonly destroyRef = inject(DestroyRef); private subs?: Subscription; constructor() { effect(() => { this.subs?.unsubscribe(); this.subs = this.setupMouseEventListener(this.scrubberTarget()); }); this.destroyRef.onDestroy(() => { document.body.classList.remove('resizing'); this.subs?.unsubscribe(); }); } private setupMouseEventListener(target: HTMLDivElement): Subscription { const mousedown$ = fromEvent<MouseEvent>(target, "mousedown"); const mousemove$ = fromEvent<MouseEvent>(document, "mousemove"); const mouseup$ = fromEvent<MouseEvent>(document, "mouseup"); return mousedown$ .pipe( tap((event) => { this.isDragging.set(true); this.startX.set(event.clientX); this.startValueAtTheTimeOfDrag.set(this.startValue()); document.body.classList.add("resizing"); }), switchMap(() => mousemove$.pipe( takeUntil( mouseup$.pipe( tap(() => { this.isDragging.set(false); document.body.classList.remove("resizing"); }) ) ) ) ) ) .subscribe((moveEvent) => { const delta = moveEvent.clientX - this.startX(); const deltaWithSensitivityCompensation = delta * this.sensitivity(); const newValue = Math.round( (this.startValueAtTheTimeOfDrag() + deltaWithSensitivityCompensation) / this.step() ) * this.step(); this.emitChange(newValue); this.startValue.set(newValue); }); } private emitChange(newValue: number): void { const clampedValue = Math.min(Math.max(newValue, this.min()), this.max()); this.scrubbing.emit(clampedValue); } }
エフェクトを使用してリスナーを開始しました。これにより、ターゲット要素が変更された場合に、新しい要素にリスナーを設定できるようになります。
Figma と同様の入力フィールドを構築するのに役立つ、非常にシンプルな Scrubber ディレクティブを Angular で作成しました。 Makes は、ユーザーが数値入力を操作するのが非常に簡単です。
https://stackblitz.com/edit/figma-like-number-input-angular?file=src/scrubber.directive.ts
ツイッター
Github
リンクトイン
コメントセクションにあなたの意見を追加してください。安全を確保してください❤️
以上がディレクティブを使用した Angular の Figma のような入力フィールドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。