Natürlich gibt es bereits mehrere Spezillionen von Online-Farbwählern, z.B. hier, hier oder hier. Und auch entspr. jQuery-Plugins. Aber darum geht es ja gar nicht 🙂 sondern um die Bastelarbeit – und die Einsicht, dass man mit rudimentären Kenntnissen von HTML, CSS und JS lustige und praktische Web-Apps bauen kann.
Inhalt
1. Worum gehts?
Um einen webbasierten, interaktiven Farbwähler, der die Farbwerte in verschiedenen Farbräumen & -modellen ausgibt. Zuerst das Ergebnis:
hue: 0°,
saturation: 0%,
brightness: 100%
hue: 0°,
saturation: 100%,
brightness: 100%
red: 0,
green: 0,
blue: 0
Einsatzmöglichkeiten sind z.B. UIs, in denen Benutzende Farben auswählen können – oder einfach ein kleines Nachschlage- und Übersetzungstool für Farbwerte.
2. Die Bestandteile
Der Color Picker hat – genauso wie z.B. der von Photoshop oder anderen Umgebungen – zwei Interaktionsmöglichkeiten. Die eine ist das "große" Farbfeld, in dem die Helligkeit und Sättigung durch Verschieben eines "Kringels" eingestellt werden kann. Die andere ist ein Schieberegler für den Farbwert. In Kombination von Farbwert, Helligkeit und Sättigung ergibt sich die endgültige Farbe. Dem liegt das gängige HSV-Modell zur Farbbeschreibung zu Grunde. Ohne hier zu tief in die Welt der Farbräume und -modelle abzutauchen, eine kurze Auffrischung: Farbwert-Sättigung-Hellwert-Farbräume (HSV für hue, saturation und value; mit entweder brightness – dann HSB – oder lightness – dann HSL – als Helligkeitswert) sind trotz komplexer Theorie recht einfach in der Handhabung und lassen sich im Webdesign via CSS als CSS-Wert hsl([hue],[saturation]%,[lightness]%)
für Farb-Parameter nutzen, z.B. als background-color:hsl(349,59%,46%);
. Neben HSV-Modellen ist vor allem das subtraktive Farbmischmodell RGB (Mischung von red, green und blue) oder das additive Mischmodell wie CMYK (Mischung von cyan, magenta, yellow und schwarz bzw. key) verbreitet. Letzteres wird vor allem im Druck angewandt und ist für leuchtende Medien wie Bildschirme eher ungeeignet, wird daher hier vernachlässigt. Die RGB-Angabe ist allerdings in CSS als rgb([rotwert],[grünwert],[blauwert])
als Eigenschaft nutzbar – z.B. background-color:rgb(191,48,72);
. Eine Variante der RGB-Codierung ist die Darstellung der drei zu mischenden Farbwerte in hexadezimaler Darstellung (HEX) als #RRGGBB
, z.B. als background-color:#bf3048;
. Alle drei Beispiele, background-color:hsl(349,59%,46%);
, background-color:rgb(191,48,72);
und background-color:#bf3048;
beschreiben übrigens die gleiche Farbe, nämlich "medienmarmela.de-rot" 🙂 Der Color Picker soll also die eingestellte Farbe in HSB, HSL, RGB und HEX ausgeben. Eine Eingabe numerischer Farbwerte und Rückübersetzung in Farbe und Position des Color Picker ist "geplant".
2.1 Das Farbfeld für Helligkeit und Sättigung
Um Helligkeit und Sättigung einer Grundfarbe zu visualisieren, liegen im <div id="colorpicker">
drei verschachtelte <divs>
übereinander: Eins mit der (über den Schieberegler einstellbaren, s.u.) Grundfarbe (#basecolor
), ein weiteres mit einem Sättigungsverlauf (#saturation
, mit einem linear-gradient
von links, weiß, nach rechts, transparent) und ein drittes mit einem Helligkeitsverlauf (#brightness
, ebenfalls mit einem linear-gradient
von unten, schwarz, nach oben, transparent):
<div id="colorpicker"> <div id="basecolor"> <div id="saturation"> <div id="brightness"></div> </div> </div> </div>
Alle drei werden absolute
übereinander positioniert und mit Grundfarbe bzw. den Verläufen bestückt:
#colorpicker { background:#333435; max-width:500px; min-height:300px; position:relative; margin:0 auto; display: flex; flex-flow: row wrap; width: calc(100% - 20px); padding:10px; min-width: 330px; } #basecolor { background:#ff0000; position:relative; width:300px; height:300px; top:0; left:0; } #saturation { position:absolute; width:100%; height:100%; top:0; left:0; background-image:linear-gradient(to right,white,rgba(255,255,255,0)); } #brightness { position:absolute; width:100%; height:100%; top:0; left:0; background-image:linear-gradient(to top,black,rgba(255,255,255,0)); }
Das Ergebnis sieht so aus:
2.2 Der Schieberegler für den Farbwert
Neben das Farbfeld basteln wir den Schieberegler für die Grundfarbe: Dafür wird ein weiteres <div> mit der id="slider"
in den #colorpicker
eingefügt:
<div id="colorpicker"> <!-- #basecolor, #saturation, #brightness, s.o. --> <div id="hue"></div> </div>
Der #hue
-Slider wird schmaler, ebenfalls absolut positioniert und mit einem etwas exotischeren Farbverlauf gestaltet: Um einmal "um den Farbkreis" zu kommen, werden die Farben Rot (#ff000
), Gelb (#ffff00
), Grün (#00ff00
), Cyan (#00ffff
), Blau (#0000ff
), Magenta (#ff00ff
) und wieder Rot in gleichgroßen Abstufungen (16.6667%
) verteilt:
#hue { height: 300px; width: 16px; position: absolute; top: 10px; left:320px; background-image: linear-gradient(to bottom, #ff0000 0%, #ffff00 16.6667%, #00ff00 33.3334%, #00ffff 50%, #0000ff 66.6667%, #ff00ff 83.3334%, #ff0000 100%); }
Ta-dah: Der Grundfarben-Schieberegler-Hintergrund:
2.3 Die Ausgabe der Farbwerte
Der Ausgabebereich hat vier Angaben: HSB, HSL, RGB und HEX und zusätzlich eine Vorschau der eingestellten Farbe. Die Angaben für HSB und HSL haben eine Gradangabe zwischen 0° und 360° (für die Position des Farbwertes auf dem Farbkreis) und zwei Prozentangaben zwischen 0% und 100%; RGB hat ebenfalls drei Werte für die drei Kanäle, jeweils zwischen 0 und 255, der HEX-Wert ist eigentlich eine Codierung dieser RGB-Werte, besteht also aus drei Zeichengruppen (die Werte von 0 bis 255 werden hier von 00 bis FF angegeben), die hintereinander in einem sechsstelligen Wert mit vorangestellter # angegeben werden. Alle Ausgaben erhalten innerhalb eines <div id="values"> einen eigenen Abschnitt, die Werte stehen in entsprechenden <span>s, in welche die Werte nach einer Benutzendeninteraktion dynamisch neu geschrieben werden können:
<div id="colorpicker"> <!-- #basecolor, #saturation, #brightness, s.o. --> <!-- #hue, s.o. --> <div id="values"> <div id="hsb"><strong>HSB:</strong><br>hue: <span class="huevalue">0</span>°,<br>saturation: <span class="saturationvalue">0</span>%,<br>brightness: <span class="brightnessvalue">100</span>%</div> <div id="hsl"><strong>HSL:</strong><br>hue: <span class="huevalue">0</span>°,<br>saturation: <span class="saturationvalue">100</span>%,<br>brightness: <span class="lightnessvalue">100</span>%</div> <div id="rgb"><strong>RGB:</strong><br>red: <span class="redvalue">0</span>,<br>green: <span class="greenvalue">0</span>,<br>blue: <span class="bluevalue">0</span></div> <div id="hex"><strong>HEX:</strong> <span class="hexvalue"> #ffffff</span></div> <div id="result"></div> </div> </div>
#values { position: absolute; height: 300px; width: 160px; top: 10px; left: 350px; box-sizing:border-box; color: #e1e2e3; font-size: .9rem; } #values strong { color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.5); } #values hr { border-bottom-color: rgba(255,255,255,0.25); border-top-color: transparent; margin: 5px 0; } #result { height: 30px; width: 100%; background-color:#fff; position: relative; }
Das Ganze sieht dann so aus:
hue: 0°,
saturation: 0%,
brightness: 100%
hue: 0°,
saturation: 100%,
brightness: 100%
red: 0,
green: 0,
blue: 0
2.4 Farbwähler und Schieberegler
Um nun den Farbwähler-Kringel und das Schieberegler-Schiebedings zu generieren, kommen sowohl zu #basecolor
als auch zu #hue
jeweils ein div für die "Aktionsfläche" (#chooserarea
und #sliderarea
) und jeweils ein div für die beweglichen Elemente (#chooser
und #slider
) hinzu. #chooserarea
und #sliderarea
sind die Bereiche, in denen #chooser
und #slider
bewegt werden können. Diese Bereiche sind jeweils ein paar Pixel größer, damit die Mitte der beweglichen Elemente auch bis ganz in die Randbereiche der Areas bewegt werden können:
<div id="colorpicker"> <div id="basecolor"> <div id="saturation"> <div id="brightness"> <div id="chooserarea"> <!-- Bereich für den Helligkeits- und Sättigungswähler --> <div id="chooser"></div> <!-- Helligkeits- und Sättigungswähler --> </div> </div> </div> </div> <div id="hue"> <div id="sliderarea"> <!-- Bereich für den Schieberegler --> <div id="slider"></div> <!-- Schieberegler --> </div> </div> <!-- #values --> </div>
#chooserarea { position: absolute; width: 310px; height: 310px; top: -5px; left: -5px; } #chooser { width: 10px; height: 10px; border: 1px solid #fff; box-sizing: border-box; border-radius: 50%; outline: 1px solid #000; cursor: move; } #sliderarea { height: 308px; width: 20px; position: absolute; top: -4px; left: -2px; } #slider { width: 20px; left: 0px; height: 8px; border: 1px solid #fff; outline: 1px solid #000; box-sizing: border-box; border-radius: 3px; cursor: n-resize; }
Damit #chooser
und #slider
"beweglich" werden, nutze ich die jQuery-Events .mousedown()
(Doku), .mousemove()
(Doku), .mouseup()
(Doku) & .mouseleave()
(Doku).
Um das mousedown- und das mousemove-Event zu kombinieren (z.B. in die freie Fläche der Helligkeitseinstellung zu klicken und direkt zu ziehen), werden die Variablen dragSlider
und dragChooser
definiert, die standardmäßig auf false
stehen. Diese werden bei mousedown mit true
überschrieben und bei mouseup und mouseleave wieder auf false
gesetzt. Nur, wenn die Variable = true
, kann das Event mousemove eine Funktion triggern. Sowohl mousedown als auch mousemove ermittelt die Position von #chooser
(X und Y) bzw. #slider
(nur Y) relativ zu der jeweiligen Interaktionsfläche (#chooserarea
bzw. #sliderarea
). Die Funktionen setChooser()
und setSlider()
validieren die Koordinaten und setzen diese als Position des jeweiligen UI-Elements via .css()
. Die Y-Koordinate des Sliders generiert automatisch die neue Hintergrundfarbe für das Helligkeits- und Sättigungsfeld. Diese Funktion ist hier allerdings erst einmal nur zu Testzwecken implementiert, die wird im weiteren Verlauf noch umgebaut.
$(function() { // declare initial variables var dragSlider = false, sliderY = 0, dragChooser = false; chooserX = 0, chooserY = 0; //cklickable and draggable brightness and saturation chooser $('#chooserarea').on('mousedown',function(e) { dragChooser = true; var parentOffset = $('#chooser').parent().offset(); chooserX = e.pageX - parentOffset.left - 5; chooserY = e.pageY - parentOffset.top - 5; setChooser(); }).on('mouseup mouseleave',function() { dragChooser = false; }).on('mousemove',function(e) { if(dragChooser) { var parentOffset = $('#chooser').parent().offset(); chooserX = e.pageX - parentOffset.left - 5; chooserY = e.pageY - parentOffset.top - 5; setChooser(); } }); //clickable and draggable hue slider $('#sliderarea').on('mousedown',function(e) { dragSlider = true; var parentOffset = $('#slider').parent().offset(); sliderY = e.pageY - parentOffset.top; setSlider(); }).on('mouseup mouseleave',function() { dragSlider = false; }).on('mousemove',function(e) { if(dragSlider) { var parentOffset = $('#slider').parent().offset(); sliderY = e.pageY - parentOffset.top; setSlider(); } }); //validate negative and too large values, set chooser position on click or drag function setChooser() { if (chooserX < 0) { chooserX = 0; } if (chooserX >300) { chooserX = 300; } if (chooserY < 0) { chooserY = 0; } if (chooserY >300) { chooserY = 300; } $('#chooser').css({'position':'absolute','top':chooserY + 'px','left':chooserX + 'px'}); } //validate negative and too large values, set slider position on click or drag function setSlider() { if (sliderY < 0) { sliderY = 0; } if (sliderY >300) { sliderY = 300; } $('#slider').css({'position':'absolute','top':sliderY + 'px'}); //FOR DEMONSTRATION PURPOSES ONLY //convert the slider height (0 - 300px) to hue degrees (0 - 360°) ... var baseColor = sliderY * 1.2; // ... and set as background-color for #basecolor $('#basecolor').css('background-color','hsl(' + baseColor + ',100%,50%)') } });
Das Ergebnis: Die Anzeige für Sättigung und Helligkeit bzw. für Farbwert ist jeweils klickbar, die Position des jeweiligen Wählers wird bei Klick an die Mausposition gesetzt. Innerhalb des mousedown-Events kann der Wähler gezogen werden (Genau dieses 'dragging' innerhalb von mousedown habe ich in jQuery UI nicht hinbekommen. Hinweise willkommen 🙂 ). Das Loslassen des Maus-Buttons (mouseup) und das Herausziehen aus der Interaktionsfläche (mouseleave) führt zum Beenden der entspr. Bewegung des Wählers. Statt der Farbwerte hier zu Demo-Zwecken die Maus-Events und die (gerundeten) Koordinaten, die man im weiteren Verlauf noch benötigt:
mouse event:
touch event:
position x:
position y:
mouse event:
touch event:
position y:
2.5 Farbwerte be- und umrechnen
Der Rest ist JavaScript und Mathematik: Speichern der Koordinaten von #chooser
und #slider
, Berechnen der HSB- und HSL-Werte
- Farbwert (hue, HHSB, HSL): Dafür wird die y-Koordinate von
#slider
benötigt, die oben bereits in der VariablesliderY
gespeichert wurde.
Hier wird sie nun mittels* 1.2
von 0-300px (Position in Px) in 0-360° (Farbwert in Grad) umgerechnet, in die Variablehue
geschrieben und abschließend im#values
-Abschnitt angezeigt. Da HHSB = HHSL ist, muss nicht umgerechnet werden 🙂
Via.css('background-color','hsl(' + hue + ',100%,50%)');
kann hier bereits die#basecolor
gesetzt werden, da man dafür nur den HHSL-Wert benötigt. Die saturation SHSL liegt bei "Vollsättigung" bei 100%, die lightness LHSL liegt "unabgedunkelt" bei 50%. - Sättigung (saturation, SHSB): Die x-Koordinate von
#chooser
wurde oben in der VariablechooserX
gespeichert, nun wird sie mittels/ 3
von 0-300px (Position in Px) in 0-100% (Sättigung in %) umgerechnet, in die VariablesaturationHSB
geschrieben und im#values
-Abschnitt von#hsb
angezeigt. - Helligkeit (brightness, BHSB): Die y-Koordinate von
#chooser
wurde oben in der VariablechooserY
gespeichert, jetzt mittels100 - (chooserY / 3)
von 300-0px (Position) in 0-100% (Helligkeit) umgerechnet, alsbrightnessHSB
gespeichert und ebenfalls im#values
-Abschnitt von#hsb
angezeigt. - Helligkeit (lightness, LHSL): Wenn man BHSB in LHSL gem. dieser Formel umrechnen möchte, ist
LHSL = BHSB * (1 – SHSB/2)
Unter Berücksichtigung der zusätzlich notwendigen Umrechnung der 0-300px in 0-100% ergibt sich untenstehende Formel fürlightnessHSL
. - Sättigung (saturation, SHSL): Hier wird es noch ein wenig wilder, da unterschiedliche Fälle unterschieden werden:
- Wenn LHSL 0 oder 100%, dann ist SHSL immer 0,
- ansonsten ist
SHSL = (BHSB – LHSL) / min (LHSL, 100 – LHSL)
Das resultieren bei mir in wilden if/else-Verschachtelungen für die Berechnung vonsaturationHSL
.
function setColor() { //generate hue from sliderY, 0-300px => 0-360 deg hue = Math.round(sliderY * 1.2); //display values $('#hsb .huevalue').text( hue ); $('#hsl .huevalue').text( hue ); // set basecolor via hsl css with saturation 100% and lightness 50% $('#basecolor').css('background-color','hsl(' + hue + ',100%,50%)'); //generate hsb saturation from chooserX, 0-300px = 0-100% saturationHSB = Math.round(chooserX / 3); //generate hsb brightness from chooserY, 300-0px = 0-100% brightnessHSB = Math.round(100 - (chooserY / 3)); //display values $('#hsb .saturationvalue').text( saturationHSB ); $('#hsb .brightnessvalue').text( brightnessHSB ); //convert hsb brightness in hsl lightness //convert hsb saturation in hsl saturation //formulas from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL lightnessHSL = Math.round(((brightnessHSB/100) * (1 - ((saturationHSB/100) / 2))) * 100); if (lightnessHSL == 0 || lightnessHSL == 100) { saturationHSL = 0; } else { if((1 - (lightnessHSL/100)) < (lightnessHSL/100)) { var smallerValue = (1- (lightnessHSL/100)); } else { var smallerValue = (lightnessHSL/100); } saturationHSL = Math.round(((brightnessHSB/100) - (lightnessHSL/100)) / smallerValue * 100); }; //display values $('#hsl .lightnessvalue').text( lightnessHSL ); $('#hsl .saturationvalue').text( saturationHSL ); }
Vorschau färben, HSL nach RGB und RGB nach HEX konvertieren Das ist tatsächlich etwas abenteuerlich. Da ich die Umrechnung in JS nicht hinbekomme :-?, gehe ich einen CSS-Umweg: HSL ist wie erwähnt im Gegensatz zu HSB prinzipiell CSS-fähig: background-color:hsl(h°,s%,l%);
funktioniert in allen gängigen Browsern. Das Element #result
wird – wie oben bereits #basecolor
– entsprechend mit den berechneten HSL-Werten eingefärbt. Mittels $(element).css('background-color);
gibt jQuery dann aber immer (hoffentlich?) die RGB-Werte als rgb([rotwert],[grünwert],[blauwert])
und nicht die HSL-Werte zurück! Diesen Rückgabe-String kann man von rgb(
und )
bereinigen und an den Kommata splitten, um an die drei Werte zu gelangen1. Diese RGB-Werte können nun in die drei HEX-Blöcke konvertiert werden2. Die errechneten Werte für R, G und B werden als #RRGGBB
im entsprechenden #values
-Abschnitt #hex
angezeigt.
//generating result preview background color with hsl css function refreshPreview() { $('#result').css('background-color','hsl(' + hue + ',' + saturationHSL + '%,' + lightnessHSL + '%)'); } //convert hsl to rgb function getRgbHex() { var result = $('#result').css('background-color'); var rgb = result.replace(/^rgba?\(|\s+|\)$/g,'').split(','); for(var i in rgb) { var redValue = rgb[0]; var greenValue = rgb[1]; var blueValue = rgb[2]; } $('#rgb .redvalue').text( redValue ); $('#rgb .greenvalue').text( greenValue ); $('#rgb .bluevalue').text( blueValue ); //convert rgb to hex format var rgb = result; function rgb2hex(rgb){ rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); return (rgb && rgb.length === 4) ? '#' + ('0' + parseInt(rgb[1],10).toString(16)).slice(-2) + ('0' + parseInt(rgb[2],10).toString(16)).slice(-2) + ('0' + parseInt(rgb[3],10).toString(16)).slice(-2) : ''; } //alert(rgb2hex(rgb)); $('#hex .hexvalue').text(rgb2hex(rgb)); }
3. Ergebnis und Download
Das Endergebnis kann man hier noch einmal begutachten:
Wie immer, hier die Downloads für die eigenen Bastelversuche und Anpassungen:
Als zip mit externer style.css und script.js sowie aktueller jquery-3.6.0.min.js gibt es das Ganze hier.
Viel Spaß damit 😉
4. Lizenzen und Fußnoten
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.
1 Den Code habe ich zum Teil von 'user372551', https://stackoverflow.com/a/3752026, CC-by-sa 2.5, Lizenzhinweise.
2 Der Code ist zum Teil von laurent, https://codepen.io/camponogara/pen/EmXmgy, MIT-Lizenz, Lizenzhinweise.