Heim >Web-Frontend >js-Tutorial >Figma-ähnliches Eingabefeld in Angular mit Direktiven
Leuten, die mit Figma vertraut sind, ist aufgefallen, dass die Eingabefelder das Ziehen unterstützen, um Werte zu erhöhen oder zu verringern. Anstatt zuerst auf das Eingabefeld zu klicken und dann die Zahl einzugeben, ist die Ziehfunktion wirklich praktisch, da Sie durch Ziehen ganz einfach den gewünschten Wert erhalten können.
Wir können so etwas mit Angular-Anweisungen erstellen. In diesem Experiment werden wir alle neuesten Funktionen von Angular verwenden.
Mal sehen, wie wir das aufbauen können.
Wir können dies tatsächlich auf verschiedene Arten tun. Wir werden dies mithilfe von Anweisungen erstellen. Wir werden dies tun, indem wir einen sehr allgemeinen Ansatz verfolgen. Auf diese Weise können wir die Logik für Dinge wie die Größenänderung von Elementen oder Seitenleisten usw. wiederverwenden.
Die Hauptlogik der Eingabe kann extrahiert und in eine Direktive gekapselt werden. Das Hauptziel besteht darin, die Mausereignisse abzuhören und die Mausbewegungen dann in einen nutzbaren Wert umzuwandeln. Um es etwas genauer zu erklären:
Wenn der Benutzer mit der Maus klickt (Mousedown-Ereignis).
Wir fangen an, auf die Mausbewegungen (Mousemove-Ereignisse) zu hören und verwenden diese Informationen, um sie in nutzbare Werte zu übersetzen.
Wenn der Benutzer den Klick loslässt, stoppen wir den Listener (Mouseup-Ereignis).
Wir werden rxjs verwenden, um die Logik etwas zu vereinfachen.
So würde der Pseudocode aussehen.
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); });
Wenn man sich den obigen Code ansieht, sollte ziemlich klar sein, was passiert. Grundsätzlich speichern wir den anfänglichen clientX-Wert, der die Position des Klicks auf der X-Achse darstellt. Sobald wir diese Informationen haben und der Benutzer die Maus bewegt, können wir das Delta aus der anfänglichen Startposition und der aktuellen X-Position berechnen.
Wir können weitere Anpassungen hinzufügen wie:
Empfindlichkeit – Der Widerstandsabstand zum Endwert wird durch die Empfindlichkeit bestimmt. Höhere Empfindlichkeitswerte bedeuten, dass der Endwert groß ist, auch wenn die Bewegung nicht so groß ist.
Schritt – legt das Schrittintervall beim Bewegen der Maus fest. Wenn der Schrittwert 1 ist, wird der Endwert in Schritten von 1 erhöht/dekrementiert.
Min – Mindestwert, der ausgegeben wird.
Max – Maximalwert, der ausgegeben wird.
So würde die endgültige Richtlinie aussehen:
@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); } }
Da wir nun die Richtlinie fertig haben, wollen wir sehen, wie wir sie tatsächlich nutzen können.
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); });
Derzeit haben wir die ScrubberTarget-Eingabe als input.required markiert, aber wir können sie tatsächlich optional machen und automatisch das elementRef.nativeElement des Hosts der Direktive verwenden, und es würde genauso funktionieren. Das ScrubberTarget wird als Eingabe bereitgestellt, falls Sie ein anderes Element als Ziel festlegen möchten.
Wir fügen dem Körper auch eine Klasse zur Größenänderung hinzu, damit wir den Größenänderungscursor richtig einstellen können.
@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); } }
Wir haben den Effekt verwendet, um den Listener zu starten. Dadurch wird sichergestellt, dass wir den Listener im Falle einer Änderung des Zielelements auf das neue Element setzen.
Wir haben in Angular eine supereinfache Scrubber-Direktive erstellt, die uns dabei helfen kann, Eingabefelder zu erstellen, die denen von Figma ähneln. Macht es dem Benutzer sehr einfach, mit numerischen Eingaben zu interagieren.
https://stackblitz.com/edit/figma-like-number-input-angular?file=src/scrubber.directive.ts
Github
Fügen Sie Ihre Gedanken im Kommentarbereich hinzu. Bleiben Sie gesund ❤️
Das obige ist der detaillierte Inhalt vonFigma-ähnliches Eingabefeld in Angular mit Direktiven. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!