Eigenständige SVG-Klaviatur - eine Datei, drei Einbindungen. Styling per Themes über Custom Properties, responsiv, und audio-agnostisch: den Ton verdrahtest du selbst über onPress.
Eine ES-Modul-Datei, kein Build, keine Dependencies. Bringt eigenes (gekapseltes) CSS und ein Theme-System mit. Jede Instanz rendert in einen eigenen Shadow Root - das Styling ist damit vollständig gegen das CSS der einbettenden Seite immun. Audio bleibt deine Sache.
// A) ESM-Import
<script type="module">
import { createKeyboard } from './piano-keyboard.js';
createKeyboard(document.querySelector('#kb'), { from:'C3', to:'C6', interactive:true });
</script>
// B) Globales window.PianoKeyboard
<script type="module" src="./piano-keyboard.js"></script>
<script type="module"> PianoKeyboard.create(el, { interactive:true }); </script>
// C) Auto-Init - gar kein eigenes JS
<script type="module" src="./piano-keyboard.js"></script>
<div data-piano data-from="c4" data-to="c5" data-theme="amber"></div>
Jedes [data-piano] wird beim Laden automatisch initialisiert. Erkannte Attribute:
data-from, data-to, data-theme, data-marks (Komma-Liste),
data-interactive, data-labels, data-white-width,
data-white-height, data-max-width. Auf die erzeugte Instanz kommst du danach
über el.pianoKeyboard (z. B. um onPress nachzurüsten).
<div data-piano data-from="c3" data-to="c6" data-interactive data-marks="c4,e4,g4"></div>
import { createKeyboard } from './piano-keyboard.js';
const kb = createKeyboard(el, {
from: 'C3', to: 'C6',
interactive: true,
theme: 'classic',
marks: ['C4', 'E4', 'G4'],
onPress(note, keyEl) { /* dein Sound */ }
});
Der Container muss ein Element sein, das einen Shadow Root erlaubt -
<div>, <section> oder <span>.
<input>, <img>, <br> gehen nicht.
from / to - Bereich als Note ('C4', 'f#3', 'Bb5') oder MIDI. Default C4–C5 (eine Oktave).interactive - Pointer- & Tastatur-Bedienung, ruft onPress(note, el). Default false.onPress(note, el) - Callback beim Anschlag. Hier bindest du deine Audio-Lib an.theme - classic · amber · blueprint · mono.marks - Notennamen für statische Markerpunkte (Akkordtöne).labels - Notennamen unter den weißen Tasten. Default true.whiteWidth / whiteHeight / blackWidth / blackHeight / padding - Tastenmaße.maxWidth - px-Obergrenze; sonst aus den Tastenmaßen abgeleitete Naturbreite.autoFlash - Anschlag-Animation beim Press von selbst. Default true.startReady - false dimmt, bis setReady(true) (z. B. während Samples laden).Eingebaut: classic, amber, blueprint, mono. Auswahl per
data-theme bzw. Option; Laufzeitwechsel ohne Re-Render - einfach container.dataset.theme setzen.
Themes sind reine Custom-Property-Sätze am Host im Light DOM; sie vererben in den Shadow Tree.
Ein eigenes Theme legst du als [data-theme]-Regel an oder überschreibst einzelne Tokens direkt am Container:
/* eigenes Theme */
.pk-host[data-theme="rosewood"] {
--pk-white-fill: #f3e7e0; --pk-black-fill: #2b1a1a;
--pk-accent: #e8a87c; --pk-bg: #1c1212;
}
/* oder nur ein Token kippen */
#kb { --pk-accent: #e8a87c; }
Verfügbare Tokens: --pk-bg, --pk-border, --pk-radius,
--pk-white-fill/-hover/-stroke,
--pk-black-fill/-hover/-stroke,
--pk-accent, --pk-accent-black, --pk-mark,
--pk-label, --pk-label-c, --pk-label-font.
Die Library macht keinen Ton. Du bindest deine Audio-Lib selbst ein und triggerst sie in
onPress - hier z. B. smplr:
import { SplendidGrandPiano } from 'smplr';
const piano = new SplendidGrandPiano(new AudioContext());
const kb = createKeyboard(el, {
interactive: true,
startReady: false, // dimmt, bis Samples geladen
onPress(note, keyEl) {
kb.spawnFloat(note, keyEl);
piano.start({ note }); // 'C4', 'F#3' … passt direkt
}
});
piano.load.then(() => kb.setReady(true));
createKeyboard(el, options) → Instanzinstance.flash(note, dur?) - kurzer Anschlaginstance.setActive(note, on?) - persistenter Aktiv-Zustandinstance.setReady(ready?) - Bedienung freischalten/dimmeninstance.spawnFloat(note, el?) - aufsteigender Notennameinstance.keyElement(note) - SVG-Element einer Tasteinstance.update(patch) - neu konfigurieren (Range, Maße, interactive …)instance.destroy() - Events lösen, Shadow leerenPianoKeyboard.noteToMidi(name) / midiToNote(midi) - KonvertierungPianoKeyboard.autoInit(root?) - alle [data-piano] initialisierenBei interactive sind die Tasten fokussierbar (roving tabindex, role="button", aria-label):
→/← bzw. ↑/↓ - Fokus zur nächsten/vorigen Taste (nach Tonhöhe)Home / End - erste / letzte TasteLeertaste / ⏎ Enter - Taste anschlagenJede Instanz rendert ihr gesamtes Painting in einen eigenen Shadow Root. Selektoren aus dem äußeren
Dokument greifen nicht hinein - Host-Regeln wie .pk-host rect { fill:lime } bleiben wirkungslos
(im Demo-Tab live geschaltet). Theming bleibt voll möglich, weil Custom Properties über die Shadow-Grenze
hinein vererben. Der Container selbst ist von außen weiter stylbar (Margin, Tokens) - das ist bewusstes
Container-Theming, kein Leck.
Zu beachten: Zugriff auf den gerenderten Inhalt (DevTools/QA) läuft über
el.shadowRoot. Mehrere Instanzen pro Seite arbeiten unabhängig.