Letzte Nacht stöberte ich in den Kellern einer besonders großen Codebasis und stolperte über unser normalize.css, das dafür sorgt, dass all unsere Markups auf verschiedenen Browsern ähnlich dargestellt werden. Ich habe es kurz überflogen und Stile für ein ziemlich eigenartiges Element namens <output> gefunden, das ich noch nie zuvor gesehen oder auch nur davon gehört hatte.
Laut MDN „stellt es das Ergebnis einer Berechnung oder einer Benutzeraktion dar“, typischerweise in Formularen verwendet. Und peinlicherweise für mich ist es keine neue und schicke Ergänzung zur Spezifikation, da Chris es bereits 2011 in einem Beitrag über value bubbles für Range-Inputs verwendet hat.
Aber egal! Was macht output und wie verwenden wir es? Nun, nehmen wir an, wir haben ein Eingabeelement mit dem type range. Dann fügen wir ein output-Element hinzu und korrelieren es mit dem Eingabeelement über sein for-Attribut.
<input type="range" name="quantity" id="quantity" min="0" max="100">
<output for="quantity"></output>
Schauen Sie sich den Pen Input Output #2 von CSS-Tricks (@css-tricks) auf CodePen an.
Es… tut eigentlich nichts. Standardmäßig hat output keine Stile und rendert keine Box oder irgendetwas im Browser. Außerdem passiert nichts, wenn wir den Wert unseres Eingabeelements ändern.
Wir müssen alles mit JavaScript verbinden. Kein Problem! Zuerst müssen wir unser Eingabeelement im DOM mit JavaScript finden, so:
const rangeInput = document.querySelector('input');
Jetzt können wir einen Event-Listener hinzufügen, sodass wir immer dann, wenn wir den Wert ändern (durch Links- oder Rechtsverschieben auf unserem Eingabeelement), eine Änderung erkennen können.
const rangeInput = document.querySelector('input');
rangeInput.addEventListener('change', function() {
console.log(this.value);
});
this.value bezieht sich immer auf den Wert von rangeInput, da wir ihn innerhalb unseres Event-Handlers verwenden, und wir können diesen Wert dann zur Überprüfung in die Konsole zurückgeben. Danach können wir unser output-Element im DOM finden.
const rangeInput = document.querySelector('input');
const output = document.querySelector('output');
rangeInput.addEventListener('change', function() {
console.log(this.value);
});
Und dann bearbeiten wir unseren Event-Listener, um den Wert von output so zu setzen, dass er sich ändert, wann immer wir den Wert des Eingabeelements ändern.
const rangeInput = document.querySelector('input');
const output = document.querySelector('output');
rangeInput.addEventListener('change', function() {
output.value = this.value;
});
Und voilà! Da haben wir es, naja, meistens. Sobald Sie den Wert des Eingabeelements ändern, spiegelt der Output dies nun wider.
Schauen Sie sich den Pen Input Output #3 von Robin Rendle (@robinrendle) auf CodePen an.
Wir sollten dies wahrscheinlich verbessern, indem wir unserem Output einen Standardwert geben, damit er sofort beim Laden der Seite sichtbar ist. Das könnten wir mit dem HTML selbst machen und den Wert innerhalb des Outputs setzen.
<output for="quantity">50</output>
Aber ich glaube, das ist nicht besonders kugelsicher. Was passiert, wenn wir das Minimum oder Maximum unseres Eingabeelements ändern wollen? Wir müssten immer auch unseren Output ändern. Lasst uns den Zustand unseres Outputs in unserem Skript setzen. Hier ist eine neue Funktion namens setDefaultState.
function setDefaultState() {
output.value = rangeInput.value;
}
Wenn das DOM fertig geladen ist, und dann rufen wir diese Funktion auf.
document.addEventListener('DOMContentLoaded', function(){
setDefaultState();
});
Schauen Sie sich den Pen Input Output #4 von Robin Rendle (@robinrendle) auf CodePen an.
Jetzt können wir alles stylen! Aber es gibt noch eine Sache. Der Event-Listener change ist gut und schön, aber er aktualisiert den Text nicht sofort, wenn Sie nach links oder rechts wischen. Glücklicherweise gibt es einen neuen Typ von Event-Listener namens input mit ziemlich guter Browserunterstützung, den wir stattdessen verwenden können. Hier ist unser gesamter Code mit dieser Ergänzung:
const rangeInput = document.querySelector('input');
const output = document.querySelector('output');
function setDefaultState() {
output.value = rangeInput.value;
}
rangeInput.addEventListener('input', function() {
output.value = this.value;
});
document.addEventListener('DOMContentLoaded', function() {
setDefaultState();
});
Schauen Sie sich den Pen Input Output #5 von Robin Rendle (@robinrendle) auf CodePen an.
Und da haben wir es! Ein Eingabeelement mit einem Output.
Ich kannte das Element bereits, aber es wirft die Frage auf: „Was ist überhaupt sein Zweck!?“ Warum nicht ein
<div>oder ein oder sogar einen anderen textbasierten Container verwenden? Semantik? Hmm, Google wird die dynamischen Inhalte nicht lesen, also vermutlich kein Nutzen dafür.Wenn es den Wert ohne JavaScript ausgeben würde, zum Beispiel eine Bindung und Funktionalität über das „for“-Attribut wie ein delegierter Klick in einem Element, wäre das hilfreich.
Aber nur ein weiteres funktionsloses Container-Element? Nee, gefällt mir nicht.
Es ist für die Barrierefreiheit – es ist angeblich automatisch mit den Eingabeelementen verbunden, die mit seinem
for-Attribut angegeben werden, und vor allem kann es mit<label>versehen werden.Wenn dies eine integrierte Interaktivität bieten würde, bei der man beispielsweise den Endwert entweder über den Schieberegler oder durch Änderung des Werts von
outputändern könnte, wäre das fantastisch; denn gute UX erreicht dies bereits durch eine Kombination ausrangeundinput.Aber Sie haben Recht… das ist… nichts. Yay, es schafft Klarheit für einen Entwickler, der Ihren Code durchsucht, aber am Ende des Tages ist es genauso effektiv, jedes andere Element zu verwenden, was schade ist. Scheint eine echte verpasste Gelegenheit zu sein.
würde mehr Sinn ergeben als
Möchten Sie teilen, warum?
@pat, ich glaube, der Grund dafür ist nur eine geringe Speichereffizienz – es ist nicht nötig, eine anonyme Funktion zu erstellen, um
setDefaultStateaufzurufen (und nichts anderes zu tun), wenn mansetDefaultStatedirekt übergeben kann.Ich würde das so machen.
Das ist meiner Meinung nach die intelligenteste Art, es zu tun.
Das ist die clevere Art, es zu tun, auch bekannt als die DRY-Methode (Don't Repeat Yourself).
Ich habe es verwendet, um Tooltips für die Daumen von Range-Schiebereglern zu erstellen.
Die Struktur, die ich verwende, ist ein Wrapper-Element mit einem Range-
input, einemlabelund einemoutput(die beiden letzteren sind über dasfor-Attribut mit deminputverbunden). Ich generiere diese Struktur mit Pug, damit ich das Minimum, das Maximum und den aktuellen Wert des Bereichs in Variablen speichern kann (Sie werden gleich sehen, warum).Im CSS setze ich
display: flexundflex-direction: column-reverseauf dem Wrapper, damit daslabelvor deminputerscheint. Ich setze dannposition: relativeauf diesen Wrapper undposition: absoluteauf dasoutput. Ich stelle auch sicher, dass sowohl derinputals auch seine Spur die gleichewidthwie der Wrapper haben.Zu diesem Zeitpunkt würde das Setzen von
left: 0auf demoutputdessen linke Kante mit der linken Kante des Wrappers ausrichten, was mit der linken Kante desinputzusammenfällt. Das Setzen vonleft: 100%würde seine linke Kante mit der rechten Kante seines Elternelements (des Wrappers) ausrichten, was mit der rechten Kante desinputzusammenfällt. Wenn wir auchtransform: translate(-50%)setzen, wird seine vertikale Mittelachse mit der linken Kante desinput(im Fallleft: 0) oder mit der rechten Kante desinputausgerichtet.Aber wenn wir möchten, dass es wie ein Tooltip für den Range-Daumen funktioniert, dann muss es sich innerhalb derselben Grenzen wie der Daumen bewegen. Der Daumen bewegt sich in Chrome innerhalb der Grenzen der
content-boxder **Spur** und in Firefox und Edge innerhalb der Grenzen dercontent-boxdes **Eingabeelements**. Aber wenn wir die gleichewidthfür den Wrapper, das eigentliche Range-Eingabeelement und seine Range-Spur haben, ohnepadding,borderund auch ohnemarginauf den inneren Elementen… dann ist diecontent-boxfür alle drei gleich.Das bedeutet, dass die zentrale vertikale Achse des Daumens sich zwischen einem halben Daumenbreite rechts von der linken Kante des Eingabeelements und einer halben Daumenbreite links von der rechten Kante des Eingabeelements bewegt.
Also setzen wir die
widthdes Wrappers (geerbt von sowohl deminputals auch seiner Spur) auf den Abstand zwischen diesen beiden Endpunkten ($d) plus die Daumenbreite ($tw), da wir an einem Ende eine halbe Daumenbreite und am anderen Ende eine zweite halbe haben. Wir setzen auchleftfür dasoutputauf eine halbe Daumenbreite (.5*$tw).Dann berechnen wir den numerischen Bewegungsbereich (Differenz zwischen dem Maximum und Minimum des Eingabeelements), die Startposition (
--pos) desoutput-Elements mithilfe der inline gesetzten CSS-Variablen und fügen diesen Positions wert dann in dietranslate()-Funktion ein.Schließlich müssen wir im JS
--valund den Wert desoutputaktualisieren, wann immer sich der Wert desinputändert.Das lässt den
outputsich mit dem Daumen des Bereichs mitbewegen. Keine weitere JS-Magie für die Positionierung, alles erledigt mit ein paar CSS-Variablen.Sie können eine voll funktionsfähige Demo davon sehen (oder wenn Video Ihr Ding ist, können Sie mich live dabei sehen, wie ich mich beim Live-Coding des Dings bei CodePenDay Hamburg blamiere… anscheinend kann ich Attributnamen nicht buchstabieren).
Es funktionierte zu dieser Zeit nicht in Edge aufgrund eines Fehlers, von dem mir gesagt wurde, dass er in der Veröffentlichung dieses Monats behoben wurde.
Schön!
Schön. Warum sowohl auf „change“ als auch auf „update“ Events hören/reagieren?
Das führt dazu, dass die SR den aktualisierten Wert zweimal liest.
Hier ist es in React, nur mal so.
Nett – Operator Mono, großartige Schriftart zum Coden!
Eine weitere nette Sache an dem Element ist, dass es implizit eine „Live-Region“ ist, sodass SR-Benutzer jedes Mal, wenn sich der Wert des Bereichs ändert, den aktualisierten Wert hören.
Wir können uns auf die explizite Beziehung zwischen
output/inputverlassen und alle davon dynamisch binden…