229 lines
7.2 KiB
CSS
229 lines
7.2 KiB
CSS
/* ===============================
|
||
Overlay Scrollbar — Cross-browser (Edge/Firefox/Chrome/Safari)
|
||
Fichier : _overlay-scrollbar.css
|
||
=============================== */
|
||
|
||
/* ---------- Thèmes & variables ---------- */
|
||
:root {
|
||
/* Couleurs (clair) — alignées sur les tokens de thème */
|
||
/* Track très discret */
|
||
--ovsb-track-bg: color-mix(in oklab, var(--border, rgba(15,23,42,0.25)) 16%, transparent);
|
||
/* Pouce (thumb) subtil basé sur --scrollbar-thumb, avec légère teinte brand */
|
||
--ovsb-thumb-bg: color-mix(
|
||
in oklab,
|
||
var(--scrollbar-thumb, rgba(100,116,139,0.55)) 80%,
|
||
color-mix(in oklab, var(--brand, #64748b) 18%, transparent) 20%
|
||
);
|
||
--ovsb-thumb-bg-active: color-mix(
|
||
in oklab,
|
||
var(--scrollbar-thumb, rgba(100,116,139,0.55)) 65%,
|
||
color-mix(in oklab, var(--brand-700, var(--brand, #64748b)) 40%, transparent) 35%
|
||
);
|
||
|
||
/* Géométrie & transitions */
|
||
--ovsb-trans: 240ms ease;
|
||
--ovsb-right: 2px; /* Décalage à droite de l’overlay */
|
||
--ovsb-thumb-height: 24px; /* Hauteur mini du thumb */
|
||
|
||
/* Épaisseurs / états
|
||
Astuce : on anime l’épaisseur via scaleX pour éviter les changements de hit-area.
|
||
Le track garde une largeur de hit-test stable. */
|
||
--ovsb-active-width: 10px; /* largeur visuelle en état "active" */
|
||
--ovsb-mini-width: 2px; /* largeur visuelle en état "mini" */
|
||
|
||
/* Ratio (mini vs active) → sert à scaleX. Si vous modifiez les largeurs, adaptez ce ratio. */
|
||
--ovsb-mini-scale: 0.2; /* 2 / 10 = 0.2 */
|
||
}
|
||
|
||
.dark,
|
||
[data-theme="dark"] {
|
||
/* Légèrement plus visible sur fond sombre mais toujours discret */
|
||
--ovsb-track-bg: color-mix(in oklab, var(--border, rgba(148,163,184,0.35)) 22%, transparent);
|
||
--ovsb-thumb-bg: color-mix(
|
||
in oklab,
|
||
var(--scrollbar-thumb, rgba(226,232,240,0.62)) 85%,
|
||
color-mix(in oklab, var(--brand-700, var(--brand, #94a3b8)) 24%, transparent) 15%
|
||
);
|
||
--ovsb-thumb-bg-active: color-mix(
|
||
in oklab,
|
||
var(--scrollbar-thumb, rgba(226,232,240,0.72)) 70%,
|
||
color-mix(in oklab, var(--brand-700, var(--brand, #94a3b8)) 45%, transparent) 30%
|
||
);
|
||
}
|
||
|
||
/* Reduced motion */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.ovsb,
|
||
.ovsb__root,
|
||
.ovsb__track,
|
||
.ovsb__thumb {
|
||
transition: none !important;
|
||
}
|
||
}
|
||
|
||
/* ========================================
|
||
1) Conteneur scrollable (hôte de l’overlay)
|
||
======================================== */
|
||
|
||
/* IMPORTANT : appliquer .ovsb-host sur l’élément QUI SCROLLE réellement */
|
||
.ovsb-host {
|
||
position: relative; /* nécessaire pour positionner l’overlay en absolute */
|
||
overflow: auto; /* c’est bien LUI qui scrolle */
|
||
/* Masquer la scrollbar native (Firefox + Edge/Chrome/Safari) */
|
||
scrollbar-width: none; /* Firefox */
|
||
-ms-overflow-style: none; /* Edge/IE legacy */
|
||
}
|
||
.ovsb-host::-webkit-scrollbar {
|
||
width: 0 !important; /* Chrome/Edge/Safari */
|
||
height: 0 !important;
|
||
background: transparent !important;
|
||
}
|
||
.ovsb-host::-webkit-scrollbar-thumb,
|
||
.ovsb-host::-webkit-scrollbar-track,
|
||
.ovsb-host::-webkit-scrollbar-corner {
|
||
background: transparent !important;
|
||
border: none !important;
|
||
}
|
||
|
||
/* Cas où le DOCUMENT scrolle (évitez si possible). Décommentez si nécessaire. */
|
||
/*
|
||
html, body {
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
html::-webkit-scrollbar,
|
||
body::-webkit-scrollbar {
|
||
width: 0 !important;
|
||
height: 0 !important;
|
||
}
|
||
*/
|
||
|
||
/* Si vous décidez de garder la native (non recommandé ici), stabilisez le gutter : */
|
||
/*
|
||
@supports (scrollbar-gutter: stable) {
|
||
.ovsb-host-native {
|
||
scrollbar-gutter: stable both-edges;
|
||
}
|
||
}
|
||
*/
|
||
|
||
/* ========================================
|
||
2) Overlay (track + thumb)
|
||
======================================== */
|
||
|
||
/* Racine overlay : ne capture pas les events hors track/thumb */
|
||
.ovsb {
|
||
position: absolute;
|
||
top: 0;
|
||
right: var(--ovsb-right);
|
||
bottom: 0;
|
||
width: 12px; /* largeur de HIT-TEST stable (garde la zone de survol constante) */
|
||
pointer-events: none; /* la racine ne capte pas, on laisse le contenu défiler naturellement */
|
||
z-index: 10;
|
||
contain: layout paint; /* isolation pour perf */
|
||
}
|
||
|
||
/* Nœud interne (utile si vous voulez des wrappers) */
|
||
.ovsb__root {
|
||
position: absolute;
|
||
inset: 0;
|
||
}
|
||
|
||
/* Piste (track) — zone cliquable/drag */
|
||
.ovsb__track {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 12px; /* hit-area stable */
|
||
border-radius: 9999px;
|
||
background: var(--ovsb-track-bg);
|
||
opacity: 0; /* géré par états */
|
||
pointer-events: auto; /* le track doit capter pour hover/drag */
|
||
transition: opacity var(--ovsb-trans);
|
||
will-change: opacity;
|
||
}
|
||
|
||
/* Thumb (manette) — épaisseur animée via scaleX (pas de shift) */
|
||
.ovsb__thumb {
|
||
position: absolute;
|
||
top: 0; /* la position Y est appliquée via transform translateY */
|
||
right: 1px; /* léger décalage pour équilibre visuel */
|
||
width: var(--ovsb-active-width);
|
||
height: var(--ovsb-thumb-height);
|
||
border-radius: 9999px;
|
||
background: var(--ovsb-thumb-bg);
|
||
/* Position & épaisseur animées :
|
||
translateY: mis à jour dynamiquement (via style inline ou CSS var)
|
||
scaleX: mini/active
|
||
*/
|
||
transform: translateY(var(--ovsb-thumb-y, 0px)) scaleX(var(--ovsb-mini-scale));
|
||
transform-origin: right center;
|
||
transition: transform var(--ovsb-trans), background-color var(--ovsb-trans), opacity var(--ovsb-trans);
|
||
opacity: 0; /* masqué par défaut */
|
||
pointer-events: auto; /* doit capter pour le drag */
|
||
will-change: transform, opacity, background-color;
|
||
cursor: grab;
|
||
}
|
||
.ovsb__thumb:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
/* ========================================
|
||
3) États (Hidden / Mini / Active)
|
||
======================================== */
|
||
|
||
/* HIDDEN : invisible, aucune interactivité */
|
||
.ovsb--hidden .ovsb__track,
|
||
.ovsb--hidden .ovsb__thumb {
|
||
opacity: 0;
|
||
pointer-events: none; /* rien ne capte, laisse le contenu réagir librement */
|
||
}
|
||
|
||
/* MINI : visible, thumb mince (scaleX mini), track discret */
|
||
.ovsb--mini .ovsb__track {
|
||
opacity: 1;
|
||
}
|
||
.ovsb--mini .ovsb__thumb {
|
||
opacity: 1;
|
||
background: var(--ovsb-thumb-bg);
|
||
transform: translateY(var(--ovsb-thumb-y, 0px)) scaleX(var(--ovsb-mini-scale));
|
||
}
|
||
|
||
/* ACTIVE : au survol de la zone scrollbar → thumb plus épais + couleur active */
|
||
.ovsb--active .ovsb__track {
|
||
opacity: 1;
|
||
}
|
||
.ovsb--active .ovsb__thumb {
|
||
opacity: 1;
|
||
background: var(--ovsb-thumb-bg-active);
|
||
transform: translateY(var(--ovsb-thumb-y, 0px)) scaleX(1);
|
||
}
|
||
|
||
/* Hover direct sur la zone track → active visuelle (utile en desktop) */
|
||
.ovsb__track:hover ~ .ovsb__thumb,
|
||
.ovsb__thumb:hover {
|
||
background: var(--ovsb-thumb-bg-active);
|
||
}
|
||
|
||
/* ========================================
|
||
4) Divers (compat & accessibilité visuelle)
|
||
======================================== */
|
||
|
||
/* Améliore la réactivité GPU (Edge/Chrome) */
|
||
.ovsb__track,
|
||
.ovsb__thumb {
|
||
backface-visibility: hidden;
|
||
}
|
||
|
||
/* Quand overlay affiché, éviter le scroll chaining vers parent si souhaité */
|
||
.ovsb-host {
|
||
overscroll-behavior: contain;
|
||
}
|
||
|
||
/* Cas tactile : permettre drag propre sans scroll involontaire horizontal */
|
||
.ovsb__thumb,
|
||
.ovsb__track {
|
||
touch-action: none;
|
||
}
|