Contrôles audio style Ableton/Traktor en Web Components. Knobs, faders, sliders, crossfader, surfaces XY, switches, VU-mètres, groupes à somme constante — rendu Canvas, zéro dépendance, accessibles au clavier.
« Potard » : c'est comme ça que les sondiers et les DJs appellent un potentiomètre. 🇫🇷
🕹️ Démo interactive : yrbane.github.io/potard — tous les contrôles manipulables en live, avec snippets et journal d'événements.
Ce sont des Custom Elements natifs : ils fonctionnent dans n'importe quelle page HTML ou framework (React, Svelte, Vue…).
npm install potard # (bientôt sur npm)
# ou en dépendance git :
npm install github:yrbane/potardimport { defineControls } from 'potard';
defineControls(); // enregistre tous les éléments (idempotent)<pt-knob min="-12" max="12" value="0" default="0" label="HI" unit="dB"></pt-knob>
<pt-fader min="0" max="1" value="0.8" label="Volume" curve="log"></pt-fader>
<pt-slider min="-12" max="12" value="3" default="0" label="Gain" unit="dB"></pt-slider>
<pt-crossfader label="X-Fade"></pt-crossfader>
<pt-xy label="Filter" min-x="20" max-x="20000" min-y="0" max-y="1"></pt-xy>
<pt-switch label="Sync" checked></pt-switch>
<pt-stepper options="lin,log,exp" value="log" label="Curve"></pt-stepper>
<pt-vumeter segments="16"></pt-vumeter>
<pt-led on color="#3ddc84"></pt-led>
<pt-button toggle>KILL</pt-button>| Élément | Rôle | Spécificités |
|---|---|---|
<pt-knob> |
Potentiomètre rotatif (anneau de valeur, bipolaire si min < 0 < max) |
drag vertical |
<pt-fader> |
Fader vertical | drag vertical |
<pt-slider> |
Slider à curseur en flèche, portion parcourue allumée (bipolaire depuis zéro si min < 0 < max) |
`orientation="h |
<pt-mix> |
Groupe de sliders liés à somme constante (budget, points, %) | total, `mode="prop |
<pt-crossfader> |
Crossfader horizontal, centré par défaut (−1 … 1) | drag horizontal |
<pt-xy> |
Surface XY (façon Kaoss pad) : deux axes indépendants | positionnement absolu au pointeur ; attrs min-x max-x value-x default-x (idem -y) ; events { x, y } |
<pt-switch> |
Interrupteur on/off (activator Ableton) | checked, role="switch", Espace/Entrée |
<pt-stepper> |
Sélecteur cranté ‹ › avec bouclage | options="a,b,c", value, flèches/molette, next()/prev() |
<pt-vumeter> |
VU-mètre à segments avec mémoire de pic | propriétés level (0…1), peak, resetPeak() ; attrs segments, `orientation="v |
<pt-led> |
LED de statut (décorative, aria-hidden) |
attrs on, color, blink ; propriété on |
<pt-button> |
Bouton momentané (press) ou verrouillé (toggle → change) |
propriété active |
| Attribut | Rôle | Défaut |
|---|---|---|
min / max |
Bornes de la valeur | 0 / 1 (crossfader : −1/1) |
value |
Valeur initiale | milieu de la plage |
default |
Valeur de retour au double-clic | milieu de la plage |
step |
Pas clavier/molette | 1 % de la course |
label |
Libellé affiché sous le contrôle et aria-label par défaut |
— |
unit |
Unité exposée en aria-valuetext (« 6 dB ») |
— |
disabled |
Désactive toute interaction (aria-disabled, opacité) |
— |
sensitivity |
Pixels de drag pour la pleine course | 200 |
curve |
Réponse du geste : lin ou log (taper audio, valeur = position²) |
lin |
label et disabled existent aussi sur pt-switch, pt-xy, pt-stepper et pt-mix.
Des sliders liés qui se partagent un total : quand l'un monte, les autres compensent.
Idéal pour répartir un budget, des points de compétence ou un pourcentage.
<pt-mix total="10" unit="€" step="0.5" mode="prop" label="Budget">
<pt-mix-track label="Courses" description="Alimentation" value="4" color="#3ddc84"></pt-mix-track>
<pt-mix-track label="Loisirs" value="3" color="#ff8a1e"></pt-mix-track>
<pt-mix-track label="Épargne" value="3" locked></pt-mix-track>
</pt-mix>Attributs de <pt-mix> :
| Attribut | Rôle | Défaut |
|---|---|---|
total |
Somme constante à répartir | 100 |
mode |
Compensation : prop (prorata), equal (parts égales), cascade (piste suivante d'abord) |
prop |
step |
Granularité — la somme reste exacte (le reste d'arrondi est absorbé) ; continu si absent | — |
unit |
Unité affichée par piste et exposée en aria-valuetext |
— |
orientation |
h : lignes empilées · v : colonnes |
h |
label / disabled |
Libellé du groupe (role="group") / désactivation globale |
— |
Attributs de <pt-mix-track> (par piste) : label, description (sous-libellé), value
(valeur initiale, normalisée pour atteindre le total), color (teinte du remplissage et de
la flèche), locked (valeur intouchable, exclue de la compensation — cadenas cliquable).
Comportement : les valeurs initiales sont normalisées vers total (pistes sans value :
partage égal du restant) ; double-clic sur une piste = répartition égale du disponible ;
la propriété track.value = x déclenche la compensation comme un geste utilisateur.
mix.addEventListener('input', (e) => {
const { values, labels, index } = (e as CustomEvent).detail;
});La logique pure est aussi exportée : normalize, adjust, equalize (types MixMode, MixConstraints).
- Drag vertical (horizontal pour le crossfader, absolu pour la surface XY) :
sensitivitypx = pleine course. - Shift pendant le drag : précision ×10.
- Double-clic : retour à la valeur
default. - Molette : incréments de
step. - Clavier : flèches (±
step),Home/End(min/max) —role="slider",aria-valuenow/aria-valuetextsynchronisés.
Les contrôles continus émettent des CustomEvent (bubbles) :
input— en continu pendant le geste ;change— au relâchement (et après double-clic, molette, clavier).
detail est la valeur (number), { x, y } pour pt-xy, boolean pour pt-switch, l'option (string) pour pt-stepper, { values, labels, index } pour pt-mix.
knob.addEventListener('input', (e) => console.log((e as CustomEvent<number>).detail));Via CSS custom properties héritées — dimensionnez l'hôte comme n'importe quel élément :
pt-knob { width: 48px; height: 56px; --ctl-accent: #19c2ff; --ctl-track: #3a4048; }
pt-fader { width: 36px; height: 160px; --ctl-accent: #ff8a1e; }
pt-slider { width: 160px; height: 36px; --ctl-accent: #19c2ff; }
pt-xy { width: 160px; height: 160px; --ctl-surface: #1f2226; }
pt-led { --led-color: #3ddc84; }
pt-button.active { background: var(--ctl-accent); }import { crossfadeGains } from 'potard';
crossfadeGains(0, 'constant-power'); // { a: 0.707, b: 0.707 } — équi-puissance
crossfadeGains(0, 'sharp'); // { a: 1, b: 1 } — cut DJRendu Canvas 2D dans le shadow DOM : aucun layout/reflow pendant le drag, redraw uniquement sur changement via requestAnimationFrame, devicePixelRatio géré.
pnpm install
pnpm test # Vitest + happy-dom (TDD)
pnpm build # tsc → dist/ (ESM + déclarations)
pnpm demo # démo interactive (Vite)MIT