Tutorials

Einen Farbwähler als Web Component von Grund auf bauen

11 Min. Lesezeit

Das native <input type="color"> ist ein grobes Werkzeug. Es delegiert an den OS-Farbwähler — der auf jeder Plattform anders aussieht, nicht gestylt werden kann und keine programmatische Kontrolle über das Layout bietet. Für Design-Tools, Barrierefreiheitsprüfer oder jedes Produkt, bei dem der Farbwähler eine zentrale Interaktion ist, muss man seinen eigenen bauen.

Web Components sind die richtige Architektur für einen Farbwähler. Sie sind Framework-unabhängig, kapseln ihren internen Zustand mit Shadow DOM, feuern Standard-DOM-Events und funktionieren in React, Vue, Angular oder purem HTML ohne Anpassung. Einmal bauen, überall verwenden.

Dieser Leitfaden implementiert einen vollständigen Farbwähler: eine 2D-Farbfläche als Canvas, einen Farbton-Schieberegler, einen Alpha-Kanal-Schieberegler, eine Hex-Eingabe und vollständige Tastaturnavigation mit ARIA-Labels.


Web Component-Grundlagen für UI

Die Custom Element API

Eine Web Component ist eine Klasse, die HTMLElement erweitert. Der Browser registriert sie mit einem Tag-Namen, und sie verhält sich wie jedes eingebaute Element — einschließlich Unterstützung für Attribute, Eigenschaften, Events und geslottete Kinder:

class ColorPicker extends HTMLElement {
  // Wird aufgerufen, wenn das Element erstmals mit dem DOM verbunden wird
  connectedCallback() {}

  // Wird aufgerufen, wenn das Element aus dem DOM entfernt wird
  disconnectedCallback() {}

  // Beobachtete Attribute lösen attributeChangedCallback aus
  static get observedAttributes() {
    return ['value', 'alpha'];
  }

  // Wird aufgerufen, wenn sich ein beobachtetes Attribut ändert
  attributeChangedCallback(name, oldValue, newValue) {}
}

customElements.define('color-picker', ColorPicker);

Verwendung überall in HTML:

<color-picker value="#FF5733" alpha="true"></color-picker>

Oder in React (mit einem Ref für die Event-Verarbeitung):

import { useRef, useEffect } from 'react';

function App() {
  const pickerRef = useRef(null);

  useEffect(() => {
    const picker = pickerRef.current;
    const handler = (e) => console.log(e.detail.hex);
    picker.addEventListener('color-change', handler);
    return () => picker.removeEventListener('color-change', handler);
  }, []);

  return <color-picker ref={pickerRef} value="#FF5733" />;
}

Shadow DOM zur Kapselung

Shadow DOM gibt der Komponente einen privaten DOM-Baum, der vom Hauptdokument isoliert ist. Externes CSS kann interne Elemente nicht versehentlich umgestalten, und interne IDs stehen nicht mit Seiten-IDs in Konflikt:

class ColorPicker extends HTMLElement {
  constructor() {
    super();
    // Shadow Root anhängen — 'open' erlaubt externem JS den Zugriff via element.shadowRoot
    this.shadow = this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadow.innerHTML = `
      <style>
        :host {
          display: inline-block;
          width: 280px;
          font-family: system-ui, -apple-system, sans-serif;
        }
        .picker-area {
          position: relative;
          width: 100%;
          height: 200px;
          border-radius: 8px;
          overflow: hidden;
          cursor: crosshair;
        }
        canvas {
          display: block;
          width: 100%;
          height: 100%;
        }
      </style>
      <div class="picker-area">
        <canvas id="color-area" width="280" height="200"></canvas>
        <div class="picker-cursor" role="presentation"></div>
      </div>
      <div class="sliders">
        <div class="slider-row">
          <canvas id="hue-slider" width="280" height="20" role="slider"
                  aria-label="Hue" aria-valuemin="0" aria-valuemax="360"></canvas>
        </div>
      </div>
    `;
  }
}

Canvas-basiertes Farbflächen-Rendering

Die Farbfläche

Die Hauptfarbfläche ist ein 2D-Verlauf: Die horizontale Achse zeigt die Sättigung (0 % bis 100 %), die vertikale Achse den Wert/die Helligkeit (100 % oben, 0 % unten). Der Farbton wird durch den Farbton-Schieberegler festgelegt. Dies ist das HSV-Farbmodell — verschieden von HSL, das die Helligkeit anders abbildet.

function drawColorArea(canvas, hue) {
  const ctx = canvas.getContext('2d');
  const { width, height } = canvas;

  // Den Basis-Farbton als horizontalen Weiß-zu-Farbton-Verlauf zeichnen
  const hueGradient = ctx.createLinearGradient(0, 0, width, 0);
  hueGradient.addColorStop(0, 'white');
  hueGradient.addColorStop(1, `hsl(${hue}, 100%, 50%)`);
  ctx.fillStyle = hueGradient;
  ctx.fillRect(0, 0, width, height);

  // Einen vertikalen transparent-zu-schwarz-Verlauf überlagern
  const darkGradient = ctx.createLinearGradient(0, 0, 0, height);
  darkGradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
  darkGradient.addColorStop(1, 'rgba(0, 0, 0, 1)');
  ctx.fillStyle = darkGradient;
  ctx.fillRect(0, 0, width, height);
}

Die Kombination eines horizontalen Weiß-zu-Farbton-Verlaufs und eines vertikalen transparent-zu-schwarz-Verlaufs erzeugt den vollständigen HSV-Raum für diesen Farbton. Die obere linke Ecke ist weiß (S=0, V=100), die obere rechte ist der reine Farbton (S=100, V=100), die untere rechte ist schwarz (beliebiges S, V=0) und die untere linke ebenfalls schwarz.

Pixelfarbe aus dem Canvas lesen

Wenn der Benutzer in der Farbfläche klickt oder zieht, die Farbe an dieser Position mit getImageData lesen:

function getColorAtPosition(canvas, x, y) {
  const ctx = canvas.getContext('2d');
  const pixel = ctx.getImageData(
    Math.max(0, Math.min(x, canvas.width - 1)),
    Math.max(0, Math.min(y, canvas.height - 1)),
    1, 1
  ).data;

  return {
    r: pixel[0],
    g: pixel[1],
    b: pixel[2],
  };
}

Den Cursor zeichnen

Der über dem Canvas überlagerte Cursor zeigt den aktuellen Auswahlpunkt an. Als absolut positionierten Kreis zeichnen:

.picker-cursor {
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid white;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

Durch Setzen von left und top als Prozentsatz der Canvas-Dimensionen positionieren:

function updateCursorPosition(cursor, x, y, canvasWidth, canvasHeight) {
  cursor.style.left = `${(x / canvasWidth) * 100}%`;
  cursor.style.top = `${(y / canvasHeight) * 100}%`;
}

Implementierung von Farbton- und Alpha-Schieberegler

Farbton-Schieberegler

Der Farbton-Schieberegler ist ein horizontales Canvas, das mit dem vollständigen Regenbogenspektrum von 0° bis 360° gefüllt ist:

function drawHueSlider(canvas) {
  const ctx = canvas.getContext('2d');
  const { width, height } = canvas;

  const gradient = ctx.createLinearGradient(0, 0, width, 0);
  gradient.addColorStop(0,     'hsl(0,   100%, 50%)');
  gradient.addColorStop(0.167, 'hsl(60,  100%, 50%)');
  gradient.addColorStop(0.333, 'hsl(120, 100%, 50%)');
  gradient.addColorStop(0.5,   'hsl(180, 100%, 50%)');
  gradient.addColorStop(0.667, 'hsl(240, 100%, 50%)');
  gradient.addColorStop(0.833, 'hsl(300, 100%, 50%)');
  gradient.addColorStop(1,     'hsl(360, 100%, 50%)');

  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, width, height);
}

function hueFromX(x, canvasWidth) {
  return Math.round((x / canvasWidth) * 360);
}

function xFromHue(hue, canvasWidth) {
  return (hue / 360) * canvasWidth;
}

Alpha-Schieberegler

Der Alpha-Schieberegler benötigt einen Schachbretthintergrund (der Standardindikator für Transparenz) unter dem Farbe-zu-transparent-Verlauf:

function drawAlphaSlider(canvas, hex) {
  const ctx = canvas.getContext('2d');
  const { width, height } = canvas;

  // Schachbrettmuster über CSS-Hintergrund auf dem Container,
  // oder manuell für Canvas-only gezeichnet:
  const size = 6;
  for (let row = 0; row < Math.ceil(height / size); row++) {
    for (let col = 0; col < Math.ceil(width / size); col++) {
      ctx.fillStyle = (row + col) % 2 === 0 ? '#CCCCCC' : '#FFFFFF';
      ctx.fillRect(col * size, row * size, size, size);
    }
  }

  // Overlay: Farbe-zu-transparent-Verlauf
  const gradient = ctx.createLinearGradient(0, 0, width, 0);
  gradient.addColorStop(0, `${hex}00`); // transparent
  gradient.addColorStop(1, hex);        // opak

  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, width, height);
}

Einheitliche Schieberegler-Interaktion

Beide Schieberegler teilen dasselbe Zieh-Interaktionsmuster. In einem wiederverwendbaren Helfer extrahieren:

function makeDraggable(element, onMove) {
  let dragging = false;

  const getX = (e) => {
    const rect = element.getBoundingClientRect();
    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
    return Math.max(0, Math.min(clientX - rect.left, rect.width));
  };

  element.addEventListener('mousedown', (e) => {
    dragging = true;
    onMove(getX(e));
  });
  element.addEventListener('touchstart', (e) => {
    dragging = true;
    onMove(getX(e));
  }, { passive: true });

  document.addEventListener('mousemove', (e) => {
    if (dragging) onMove(getX(e));
  });
  document.addEventListener('touchmove', (e) => {
    if (dragging) onMove(getX(e));
  }, { passive: true });

  document.addEventListener('mouseup', () => { dragging = false; });
  document.addEventListener('touchend', () => { dragging = false; });
}

Die mousemove- und mouseup-Handler auf document-Ebene stellen sicher, dass das Ziehen auch dann fortgesetzt wird, wenn der Cursor die Grenzen des Schiebereglers verlässt — entscheidend für die Benutzerfreundlichkeit.


Farbwechsel-Events auslösen

Custom Event-Muster

CustomEvent mit einem detail-Payload verwenden, der die vollständigen Farbdaten enthält. Mit einem Namespace-Präfix benennen, um Konflikte zu vermeiden:

function dispatchColorChange(element, color) {
  const event = new CustomEvent('color-change', {
    bubbles: true,        // Propagiert im DOM-Baum nach oben
    composed: true,       // Überquert Shadow DOM-Grenzen
    detail: {
      hex: color.hex,
      rgb: color.rgb,
      hsl: color.hsl,
      alpha: color.alpha,
      hexWithAlpha: color.alpha < 1
        ? `${color.hex}${Math.round(color.alpha * 255).toString(16).padStart(2, '0').toUpperCase()}`
        : color.hex,
    },
  });
  element.dispatchEvent(event);
}

composed: true ist essenziell — ohne es werden innerhalb von Shadow DOM ausgelöste Events nicht über die Grenze übertragen und sind für Code im Hauptdokument unsichtbar.

Internes Zustandsmanagement

Den Farbzustand als HSV + Alpha verwalten (das natürlichste Modell für einen visuellen Wähler) und bei der Ausgabe in andere Formate konvertieren:

class ColorPicker extends HTMLElement {
  #state = {
    h: 0,    // Farbton 0-360
    s: 100,  // Sättigung 0-100 (HSV)
    v: 100,  // Wert/Helligkeit 0-100 (HSV)
    a: 1,    // Alpha 0-1
  };

  get #hex() {
    const { r, g, b } = hsvToRgb(this.#state.h, this.#state.s, this.#state.v);
    return `#${[r, g, b].map(v => v.toString(16).padStart(2, '0')).join('').toUpperCase()}`;
  }

  #update(changes) {
    this.#state = { ...this.#state, ...changes };
    this.#render();
    dispatchColorChange(this, {
      hex: this.#hex,
      rgb: hsvToRgb(this.#state.h, this.#state.s, this.#state.v),
      hsl: rgbToHsl(hsvToRgb(this.#state.h, this.#state.s, this.#state.v)),
      alpha: this.#state.a,
    });
  }
}

Die #update-Methode wird aufgerufen, wenn sich ein beliebiger Teil der Farbe ändert — beim Ziehen des Farbton-Schiebereglers, beim Klicken auf die Farbfläche oder beim Eingeben in das Hex-Feld. Sie aktualisiert den Zustand, rendert betroffene Canvas-Elemente neu und feuert das Event.

Hex-Eingabe-Synchronisierung

Das Hex-Eingabefeld soll die aktuelle Farbe widerspiegeln und Benutzereingaben akzeptieren:

setupHexInput(hexInput) {
  // Zustand bei jeder Aktualisierung in die Eingabe spiegeln
  this.addEventListener('_internal-state-change', () => {
    if (document.activeElement !== hexInput) {
      hexInput.value = this.#hex;
    }
  });

  // Eingabe parsen und Zustand aktualisieren, wenn der Benutzer tippt
  hexInput.addEventListener('input', (e) => {
    const raw = e.target.value.trim();
    const match = raw.match(/^#?([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/);
    if (!match) return; // Unvollständige Eingabe still ignorieren

    const full = match[1].length === 3
      ? match[1].split('').map(c => c + c).join('')
      : match[1];

    const r = parseInt(full.slice(0, 2), 16);
    const g = parseInt(full.slice(2, 4), 16);
    const b = parseInt(full.slice(4, 6), 16);
    const { h, s, v } = rgbToHsv(r, g, b);
    this.#update({ h, s, v });
  });
}

Das Überspringen der Aktualisierung, wenn die Eingabe fokussiert ist, verhindert eine Rückkopplungsschleife, bei der das Eingeben von #FF eine Zustandsaktualisierung auslöst, die das Hex-Eingabefeld mitten im Tippen überschreibt.


Barrierefreiheit: Tastaturnavigation, ARIA-Labels, Screenreader

Die Barrierefreiheits-Herausforderung

Ein Canvas-Element ist für Screenreader undurchsichtig — getImageData gibt Pixelwerte zurück, aber diese Pixel vermitteln assistiver Technologie keine semantische Bedeutung. Einen Farbwähler barrierefrei zu machen erfordert das Überlagern semantischer Informationen auf das visuelle Canvas.

ARIA-Rollen für Schieberegler

Die Farbfläche, der Farbton-Schieberegler und der Alpha-Schieberegler funktionieren alle als zweidimensionale Schieberegler. role="slider" mit dem vollständigen ARIA-Schieberegler-Attributsatz verwenden:

<!-- Farbfläche: 2D, zwei separate Schieberegler für die Achsen verwenden -->
<div
  role="slider"
  id="color-area-saturation"
  aria-label="Color saturation"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-valuenow="75"
  aria-valuetext="75% saturation"
  tabindex="0"
></div>

<div
  role="slider"
  id="color-area-brightness"
  aria-label="Color brightness"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-valuenow="90"
  aria-valuetext="90% brightness"
  tabindex="0"
></div>

<!-- Farbton-Schieberegler -->
<div
  role="slider"
  id="hue-slider"
  aria-label="Hue"
  aria-valuemin="0"
  aria-valuemax="360"
  aria-valuenow="120"
  aria-valuetext="120 degrees, green"
  tabindex="0"
></div>

aria-valuenow und aria-valuetext in der #update-Methode aktualisieren:

#updateAriaAttributes() {
  const hueEl = this.shadow.querySelector('#hue-slider');
  hueEl.setAttribute('aria-valuenow', this.#state.h);
  hueEl.setAttribute('aria-valuetext', `${this.#state.h} degrees`);

  const satEl = this.shadow.querySelector('#color-area-saturation');
  satEl.setAttribute('aria-valuenow', Math.round(this.#state.s));
  satEl.setAttribute('aria-valuetext', `${Math.round(this.#state.s)}% saturation`);
}

Tastaturnavigation

Jedes Schieberegler-Element muss auf Pfeiltasten reagieren. Die standardmäßige Schieberegler-Tastaturinteraktion ist: - Pfeil rechts / Pfeil oben: Wert um eine Einheit erhöhen (oder 5 mit Umschalt für gröbere Schritte) - Pfeil links / Pfeil unten: Wert verringern - Pos1: Auf Minimum setzen - Ende: Auf Maximum setzen

setupKeyboardNavigation(hueSlider) {
  hueSlider.addEventListener('keydown', (e) => {
    let delta = 0;
    const step = e.shiftKey ? 10 : 1;

    switch (e.key) {
      case 'ArrowRight':
      case 'ArrowUp':
        delta = step;
        break;
      case 'ArrowLeft':
      case 'ArrowDown':
        delta = -step;
        break;
      case 'Home':
        this.#update({ h: 0 });
        e.preventDefault();
        return;
      case 'End':
        this.#update({ h: 360 });
        e.preventDefault();
        return;
      default:
        return;
    }

    e.preventDefault();
    this.#update({ h: Math.max(0, Math.min(360, this.#state.h + delta)) });
  });
}

Für die 2D-Farbfläche alle vier Pfeiltasten verarbeiten, um sich in beide Dimensionen zu bewegen:

setupColorAreaKeyboard(colorArea) {
  colorArea.addEventListener('keydown', (e) => {
    const step = e.shiftKey ? 5 : 1;
    let { s, v } = this.#state;

    switch (e.key) {
      case 'ArrowRight': s = Math.min(100, s + step); break;
      case 'ArrowLeft':  s = Math.max(0, s - step); break;
      case 'ArrowUp':    v = Math.min(100, v + step); break;
      case 'ArrowDown':  v = Math.max(0, v - step); break;
      default: return;
    }

    e.preventDefault();
    this.#update({ s, v });
  });
}

Live-Region für Screenreader-Ankündigungen

Damit ein Screenreader die ausgewählte Farbe ankündigt, wenn der Benutzer mit dem Wähler interagiert, eine ARIA-Live-Region hinzufügen:

<div
  role="status"
  aria-live="polite"
  aria-atomic="true"
  class="sr-only"
  id="color-announcement"
></div>

Bei jeder Farbänderung aktualisieren:

#announceColor() {
  const announcement = this.shadow.querySelector('#color-announcement');
  const { h, s, v, a } = this.#state;
  announcement.textContent = `Selected color: ${this.#hex}, ` +
    `hue ${h} degrees, saturation ${Math.round(s)}%, ` +
    `brightness ${Math.round(v)}%` +
    (a < 1 ? `, ${Math.round(a * 100)}% opacity` : '');
}

Die Einstellung aria-live="polite" kündigt Änderungen an, wenn der Benutzer nicht aktiv mit anderen Elementen interagiert. Für einen Farbwähler, bei dem der Benutzer wahrscheinlich zieht, ist polite korrekt — assertive würde jede Ankündigung mit der nächsten unterbrechen.

Fokus-Indikatoren

Sicherstellen, dass Fokusringe innerhalb des Shadow DOM sichtbar sind. Die Pseudoklasse :focus-visible bietet nur-Tastatur-Fokusringe, ohne Mausinteraktionen zu stören:

/* Innerhalb des Shadow DOM <style> */
[role="slider"]:focus-visible {
  outline: 2px solid #2563EB;
  outline-offset: 2px;
}

.hex-input:focus-visible {
  outline: 2px solid #2563EB;
  outline-offset: 1px;
  border-radius: 4px;
}

Die vollständige barrierefreie Komponente

Alles zusammenfassend richtet connectedCallback der Komponente alle interaktiven Elemente mit ihren ARIA-Rollen und Tastatur-Handlern ein, bevor eine visuelle Interaktion stattfindet:

connectedCallback() {
  this.shadow.innerHTML = this.#template();
  this.#setupColorArea();
  this.#setupHueSlider();
  this.#setupAlphaSlider();
  this.#setupHexInput();
  this.#setupKeyboardNavigation();
  this.#render();
}

#template() {
  return `
    <style>${this.#css()}</style>
    <div class="color-picker" role="group" aria-label="Color picker">

      <!-- 2D Farbfläche -->
      <div class="picker-area-wrapper">
        <canvas id="color-area" width="280" height="200"
          aria-hidden="true"></canvas>
        <div class="cursor" role="presentation"></div>
        <div id="color-area-interactive"
          role="slider"
          aria-label="Color saturation and brightness"
          aria-valuemin="0" aria-valuemax="100"
          aria-valuenow="100" aria-valuetext="full saturation, full brightness"
          tabindex="0"
          class="interactive-overlay">
        </div>
      </div>

      <!-- Farbton-Schieberegler -->
      <div class="slider-wrapper">
        <canvas id="hue-canvas" width="280" height="20" aria-hidden="true"></canvas>
        <div id="hue-thumb" class="slider-thumb" role="slider"
          aria-label="Hue" aria-valuemin="0" aria-valuemax="360"
          aria-valuenow="0" aria-valuetext="0 degrees, red"
          tabindex="0"></div>
      </div>

      <!-- Alpha-Schieberegler -->
      <div class="slider-wrapper" id="alpha-wrapper">
        <canvas id="alpha-canvas" width="280" height="20" aria-hidden="true"></canvas>
        <div id="alpha-thumb" class="slider-thumb" role="slider"
          aria-label="Opacity" aria-valuemin="0" aria-valuemax="100"
          aria-valuenow="100" aria-valuetext="100% opacity"
          tabindex="0"></div>
      </div>

      <!-- Hex-Eingabe -->
      <div class="hex-row">
        <label for="hex-input" class="hex-label">HEX</label>
        <input id="hex-input" type="text" class="hex-input"
          value="#FF5733" maxlength="7"
          aria-label="Hex color code"
          autocomplete="off" spellcheck="false" />
      </div>

      <!-- Screenreader-Live-Region -->
      <div role="status" aria-live="polite" aria-atomic="true"
        id="color-announcement" class="sr-only"></div>
    </div>
  `;
}

Das aria-hidden="true" auf den Canvas-Elementen teilt Screenreadern mit, den Pixelinhalt vollständig zu ignorieren — die semantische Bedeutung wird durch die interaktiven Overlay-Elemente daneben bereitgestellt.


Veröffentlichen und Verwenden der Komponente

Auf npm veröffentlichen

Eine Web Component ist nur ein JavaScript-Modul. Mit einem minimalen package.json veröffentlichen:

{
  "name": "@yourscope/color-picker",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/color-picker.js",
  "exports": {
    ".": "./dist/color-picker.js"
  },
  "files": ["dist/"]
}

In einem beliebigen Projekt importieren:

import '@yourscope/color-picker';

// Jetzt ist <color-picker> global registriert

Mit dem ColorFYI-Konverter verwenden

Den Farbwähler mit der Farbkonverter-API koppeln, um vollständige Farbdaten neben dem visuellen Wähler anzuzeigen:

document.querySelector('color-picker').addEventListener('color-change', async (e) => {
  const hex = e.detail.hex.replace('#', '');
  const response = await fetch(`/api/color/${hex}`);
  const data = await response.json();

  document.querySelector('#hex-display').textContent = data.hex;
  document.querySelector('#rgb-display').textContent = data.rgb.css;
  document.querySelector('#hsl-display').textContent = data.hsl.css;
  document.querySelector('#oklch-display').textContent = data.oklch.css;
});

Und den Palettengenerator verwenden, um harmonische Farben für jeden ausgewählten Wert anzuzeigen — und so ein vollständiges Farbentdeckungs-Erlebnis rund um den Wähler zu schaffen.


Das Wichtigste in Kürze

  • Web Components mit Shadow DOM sind Framework-unabhängig, stilgekapselt und feuern Standard-DOM-Events — die richtige Architektur für wiederverwendbare UI wie Farbwähler.
  • Die Farbfläche verwendet zwei überlagerte Canvas-Verläufe: horizontal Weiß-zu-Farbton für Sättigung, vertikal transparent-zu-schwarz für Wert/Helligkeit. getImageData liest die Pixelfarbe an jeder Ziehposition.
  • Farbton- und Alpha-Schieberegler verwenden dasselbe ziehbare Canvas-Muster; entscheidend ist es, mousemove und mouseup an document zu hängen, nicht an das Element, damit das Ziehen auch funktioniert, wenn der Cursor den Schieberegler verlässt.
  • CustomEvent mit composed: true auslösen, damit Events Shadow DOM-Grenzen überqueren und für übergeordneten Dokumentcode sichtbar sind.
  • Canvas-Elemente sind für Screenreader undurchsichtig — semantische Bedeutung durch role="slider"-Overlay-Elemente bereitstellen, mit aria-valuenow und aria-valuetext, die bei jeder Zustandsänderung aktualisiert werden.
  • Vollständige Pfeiltastennavigation auf jedem Schieberegler implementieren: Einzelschritt bei Pfeiltasten, Fünfschritt mit Umschalt, Pos1/Ende für Min/Max.
  • Eine aria-live="polite"-Statusregion hinzufügen, die die vollständige Farbbeschreibung (Hex, Farbton, Sättigung, Helligkeit, Opazität) bei jeder Änderung ankündigt.
  • Mit der Farbkonverter-API kombinieren, um vollständige Format-Aufschlüsselungen anzuzeigen, und dem Palettengenerator, um harmonische Farben neben jedem gewählten Wert zu zeigen.

Ähnliche Farben

Ähnliche Marken

Ähnliche Werkzeuge