在 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
与我联系
请在评论部分发表你的想法。注意安全❤️
