Maison >interface Web >js tutoriel >Figma comme champ de saisie dans Angular en utilisant des directives
Les personnes qui connaissent Figma auraient remarqué que les champs de saisie prennent en charge le glisser pour augmenter ou diminuer les valeurs. Au lieu d'avoir à cliquer d'abord sur le champ de saisie, puis à saisir le numéro, la fonction de glissement est très pratique car vous pouvez facilement obtenir la valeur souhaitée en faisant glisser.
Nous pouvons construire quelque chose comme ça en utilisant les directives Angular. Nous utiliserons toutes les dernières fonctionnalités d'Angular dans cette expérience.
Voyons comment nous pouvons construire cela.
Nous pouvons en fait le faire de plusieurs manières. Nous allons construire cela à l'aide de directives. Nous allons y parvenir en adoptant une approche très générique. De cette façon, nous pouvons réutiliser la logique pour des choses comme le redimensionnement d'éléments ou de barres latérales, etc.
La logique principale de l'entrée peut être extraite et encapsulée dans une directive. L'objectif principal est d'écouter les événements de la souris puis de traduire les mouvements de la souris en une valeur utilisable. Pour expliquer un peu plus en détail :
Lorsque l'utilisateur clique sur la souris (événement mousedown).
Nous commençons à écouter les mouvements de la souris (événements mousemove) et utilisons ces informations pour les traduire en valeurs utilisables.
Lorsque l'utilisateur relâche le clic, nous arrêtons l'écouteur (événement mouseup).
Nous utiliserons rxjs pour simplifier un peu la logique.
Voici à quoi ressemblerait le pseudocode.
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); });
En regardant le code ci-dessus, ce qui se passe devrait être assez clair. Nous enregistrons essentiellement la valeur clientX initiale, qui est la position du clic sur l'axe X. Une fois que nous avons cette information, lorsque l'utilisateur déplace la souris, nous pouvons calculer le delta à partir de la position de départ initiale et de la position X actuelle.
Nous pouvons ajouter davantage de personnalisations telles que :
Sensibilité - la distance de traînée jusqu'à la valeur finale sera décidée par la sensibilité. Des valeurs de sensibilité plus élevées signifient que la valeur finale sera grande même si le mouvement n'est pas si important.
Step - définit l'intervalle de pas lors du déplacement de la souris. Si la valeur du pas est 1, la valeur finale est incrémentée/décrémentée par pas de 1.
Min - valeur minimale qui sera émise.
Max - valeur maximale qui sera émise.
Voici à quoi ressemblerait la directive finale :
@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); } }
Maintenant que la directive est prête, voyons comment nous pouvons réellement commencer à l'utiliser.
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); });
Actuellement, nous avons marqué l'entrée scrubberTarget comme input.required, mais nous pouvons en fait la rendre facultative et utiliser automatiquement l'élément elementRef.nativeElement de l'hôte de la directive, et cela fonctionnerait de la même manière. Le scrubberTarget est exposé en tant qu'entrée au cas où vous souhaiteriez définir un élément différent comme cible.
Nous ajoutons également une classe de redimensionnement au corps afin de pouvoir définir correctement le curseur de redimensionnement.
@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); } }
Nous avons utilisé l'effet pour démarrer l'écouteur, cela garantirait que lorsqu'un élément cible change, nous définissons l'écouteur sur le nouvel élément.
Nous avons créé une directive Scrubber super simple dans Angular qui peut nous aider à créer des champs de saisie similaires à ceux de Figma. Il est très facile pour l'utilisateur d'interagir avec les entrées numériques.
https://stackblitz.com/edit/figma-like-number-input-angular?file=src/scrubber.directive.ts
Github
Ajoutez vos réflexions dans la section commentaires. Restez en sécurité ❤️
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!