在 Angular 中使用指令实现 Figma 类输入字段
熟悉 Figma 的人会注意到,输入字段支持拖动来增加或减少值。拖动功能非常方便,您无需先单击输入字段,然后输入数字,只需拖动即可轻松获得所需的值。
我们可以使用 Angular 指令构建类似的东西。我们将在此实验中使用 Angular 的所有最新功能。
让我们看看如何构建它。
实际上,我们可以用多种方式来实现这一点。我们将使用指令来构建它。我们将采用一种非常通用的方法来实现这一点。这样,我们可以重复使用逻辑来调整元素或侧边栏的大小等。
Scrubber 指令 - 核心功能
输入的主要逻辑可以提取出来并封装成指令。主要目的是监听鼠标事件,然后将鼠标移动转换为可用的值。更详细地解释一下:
我们将使用“rxjs”来稍微简化逻辑。
伪代码如下所示。
const mousedown$ = fromEvent(target, 'mousedown'); const mousemove$ = fromEvent (document, 'mousemove'); const mouseup$ = fromEvent (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 位置的增量。
我们可以进一步添加更多自定义功能,例如:
最终的指令如下:
@Directive({ selector: "[scrubber]", }) export class ScrubberDirective { public readonly scrubberTarget = input.required({ alias: "scrubber", }); public readonly step = model (1); public readonly min = model (0); public readonly max = model (100); public readonly startValue = model(0); public readonly sensitivity = model(0.1); public readonly scrubbing = output (); 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 (target, "mousedown"); const mousemove$ = fromEvent (document, "mousemove"); const mouseup$ = fromEvent (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); } }
如何使用 Scrubber 指令
现在我们已经准备好指令,让我们看看如何实际开始使用它。
目前,我们已将“scrubberTarget”输入标记为“input.required”,但我们实际上可以将其设为可选,并自动使用指令主机的“elementRef.nativeElement”,其工作原理相同。如果您想要将其他元素设置为目标,则“scrubberTarget”将作为输入公开。
我们还向主体添加了一个类“resizing”,以便我们可以正确设置调整大小光标。
.resizing { cursor: ew-resize; touch-action: none; -webkit-user-select: none; user-select: none; }
我们已经使用“effect”来启动监听器,这将确保当目标元素发生变化时,我们在新元素上设置监听器。
观看实际操作
我们在 Angular 中创建了一个非常简单的 Scrubber 指令,它可以帮助我们构建类似于 Figma 的输入字段。让用户可以非常轻松地与数字输入进行交互。
代码和演示
https://stackblitz.com/edit/figma-like-number-input-angular?file=src%2Fscrubber.directive.ts
与我联系
请在评论部分发表你的想法。注意安全❤️
