熟悉 Figma 的人會注意到,輸入欄位支援拖曳來增加或減少值。拖曳功能非常方便,您可以透過拖曳輕鬆獲得所需的值,而不必先按一下輸入字段,然後輸入數字。
我們可以使用 Angular 指令來建構類似的東西。我們將在本實驗中使用 Angular 的所有最新功能。
讓我們看看如何建造它。
我們實際上可以透過多種方式做到這一點。我們將使用指令來建構它。我們要做到這一點的方法是採用一種非常通用的方法。這樣,我們就可以重複使用邏輯來調整元素或側邊欄的大小等。
輸入的主要邏輯可以被提取並封裝成指令。主要目標是監聽滑鼠事件,然後將滑鼠移動轉換為可用值。更詳細解釋:
當使用者點擊滑鼠時(mousedown 事件)。
我們開始監聽滑鼠移動(mousemove 事件)並使用該資訊將其轉換為可用值。
當使用者釋放點擊時,我們停止監聽器(mouseup 事件)。
我們將使用 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); });
看看上面的程式碼,應該很清楚發生了什麼事。我們基本上儲存了初始的clientX值,也就是點擊在X軸上的位置。一旦我們有了這些訊息,當使用者移動滑鼠時,我們就可以計算初始起始位置和當前 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); } }
我們使用effect來啟動監聽器,這將確保當目標元素發生變化時,我們在新元素上設定監聽器。
我們在 Angular 中製作了一個超級簡單的 Scrubber 指令,它可以幫助我們建立類似 Figma 的輸入欄位。使用戶與數位輸入互動變得非常容易。
https://stackblitz.com/edit/figma-like-number-input-angular?file=src/scrubber.directive.ts
推特
Github
請在評論部分中添加您的想法。確保安全❤️
以上是Figma 類似於 Angular 中使用指令的輸入字段的詳細內容。更多資訊請關注PHP中文網其他相關文章!