Laboratoire CSS scroll-state()
by SonicGecko23155 lines13.1 KB
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Laboratoire CSS scroll-state()</title>
<style>
:root{color-scheme:dark;--bg:#07161a;--panel:#0d252a;--ink:#e8fbf7;--muted:#a9c9c3;--a:#66e3c4;--b:#ffc76b;--line:#1d4b50}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(circle at 15% 0,#12383d 0,transparent 38rem),var(--bg);color:var(--ink);font:16px/1.62 system-ui,sans-serif}
main{width:min(1040px,calc(100% - 28px));margin:auto;padding:38px 0 54px}
header,.card{background:color-mix(in srgb,var(--panel) 92%,transparent);border:1px solid var(--line);border-radius:22px;box-shadow:0 18px 55px #0006}
header{padding:clamp(24px,5vw,54px);position:relative;overflow:hidden}
header:after{content:"";position:absolute;width:180px;height:180px;border:34px solid #66e3c41c;border-radius:50%;right:-55px;top:-55px}
.eyebrow{color:var(--a);font-weight:800;letter-spacing:.12em;text-transform:uppercase;font-size:.76rem}
h1,h2,h3{line-height:1.14;margin:.35em 0}.hero h1{font-size:clamp(2.2rem,7vw,4.9rem);max-width:12ch}.lead{max-width:72ch;color:#c8e3de;font-size:1.08rem}
a{color:#07161a;background:var(--a);padding:.5rem .78rem;border-radius:10px;font-weight:850;text-decoration:none;display:inline-block}
a:focus-visible,button:focus-visible,input:focus-visible{outline:3px solid #fff;outline-offset:3px}
.grid{display:grid;grid-template-columns:1.1fr .9fr;gap:18px;margin-top:18px}.card{padding:24px}.full{grid-column:1/-1}
.chips{display:flex;flex-wrap:wrap;gap:8px;margin:16px 0}.chip{border:1px solid var(--line);border-radius:999px;padding:6px 10px;color:var(--muted);font-size:.88rem}.chip.on{color:#062018;background:var(--a);border-color:var(--a)}
.lab{display:grid;grid-template-columns:minmax(0,1fr) minmax(250px,.75fr);gap:20px;align-items:start}
.scroller{height:360px;overflow:auto;scroll-snap-type:y mandatory;border:1px solid #397078;border-radius:16px;background:#061115;position:relative}
.scene{height:310px;scroll-snap-align:start;padding:22px;display:flex;flex-direction:column;justify-content:space-between;border-bottom:1px solid #20464b;background:linear-gradient(145deg,#102b30,#08191d)}
.scene:nth-child(2){background:linear-gradient(145deg,#243221,#0f1d15)}.scene:nth-child(3){background:linear-gradient(145deg,#33251b,#1d140d)}
.sticky{position:sticky;top:0;z-index:3;padding:10px 14px;background:#09272d;border-bottom:1px solid var(--a);font-weight:800;container-type:scroll-state;transition:.2s}
.sticky span{display:block}
@container scroll-state(stuck:top){.sticky span{color:#07161a;background:var(--a);border-radius:8px;padding:3px 8px}}
.meter{height:12px;background:#061115;border-radius:99px;overflow:hidden;border:1px solid #28555b}.meter i{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--a),var(--b));transition:width .15s}
.readout{display:grid;gap:10px}.state{padding:12px 14px;background:#07191d;border:1px solid #1e4449;border-radius:12px}.state strong{display:block;color:var(--a);font-size:1.05rem}.state small{color:var(--muted)}
button{border:0;border-radius:12px;background:var(--b);color:#231500;padding:11px 14px;font-weight:850;cursor:pointer}
code,pre{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}pre{white-space:pre-wrap;background:#051014;border:1px solid #183b40;border-radius:14px;padding:16px;overflow:auto;color:#d8faf3}
table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:11px;border-bottom:1px solid var(--line);vertical-align:top}th{color:var(--a)}
.note{border-left:4px solid var(--b);padding:2px 0 2px 14px;color:#f1dfbd}.muted{color:var(--muted)}
footer{margin-top:18px;color:var(--muted);text-align:center}.footerlink{margin-top:12px}
@media(max-width:760px){.grid,.lab{grid-template-columns:1fr}.card{padding:18px}.scroller{height:330px}table{font-size:.9rem}th,td{padding:8px 5px}.hero h1{font-size:2.45rem}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
</style>
</head>
<body>
<main>
<header class="hero">
<p class="eyebrow">Explorable CSS, édition française</p>
<h1>Lire l'état du défilement avec CSS</h1>
<p class="lead"><code>scroll-state()</code> ajoute une question contextuelle aux requêtes de conteneur. Au lieu de demander seulement quelle taille possède un composant, la feuille de style peut demander si un élément collant est réellement bloqué, si une cible est alignée sur un point d'accrochage, ou si une zone peut encore défiler.</p>
<p><a href="https://armcp.net/">Voir la référence ARMCP</a></p>
<div class="chips" aria-label="Les quatre états étudiés"><span class="chip on">stuck</span><span class="chip">snapped</span><span class="chip">scrollable</span><span class="chip">scrolled</span></div>
</header>
<div class="grid">
<section class="card full" aria-labelledby="labtitle">
<p class="eyebrow">Expérience</p>
<h2 id="labtitle">Faites défiler le banc d'essai</h2>
<p class="muted">Le panneau de droite observe le mouvement avec JavaScript pour rendre la démonstration compréhensible dans tous les navigateurs. La barre collante utilise aussi la requête CSS native quand elle est disponible. Cette séparation évite de présenter une simulation comme une preuve de prise en charge.</p>
<div class="lab">
<div class="scroller" id="scroller" tabindex="0" aria-label="Zone de démonstration à faire défiler">
<div class="sticky"><span>En-tête collant observé</span></div>
<article class="scene"><div><p class="eyebrow">Scène 1</p><h3>Départ</h3><p>À cet endroit, le contenu reste disponible vers le bas. Faites glisser, utilisez la molette ou les touches du clavier.</p></div><div class="meter" aria-hidden="true"><i id="bar"></i></div></article>
<article class="scene"><div><p class="eyebrow">Scène 2</p><h3>Point d'accrochage</h3><p>Cette carte est une cible de <code>scroll-snap</code>. Une requête <code>snapped</code> peut styliser ses descendants lorsque le moteur la considère comme cible active.</p></div></article>
<article class="scene"><div><p class="eyebrow">Scène 3</p><h3>Fin de parcours</h3><p>Une requête <code>scrollable: bottom</code> cesse de correspondre quand il ne reste plus de contenu atteignable vers le bas.</p></div></article>
</div>
<aside class="readout" aria-live="polite">
<div class="state"><strong id="support">Détection en cours</strong><small>Prise en charge de la syntaxe native</small></div>
<div class="state"><strong id="progress">0 %</strong><small>Progression mesurée dans ce banc d'essai</small></div>
<div class="state"><strong id="direction">Aucun mouvement</strong><small>Dernière direction observée</small></div>
<div class="state"><strong id="remaining">Défilement possible vers le bas</strong><small>Équivalent pédagogique de <code>scrollable</code></small></div>
<button id="next" type="button">Aller à la scène suivante</button>
</aside>
</div>
</section>
<section class="card" aria-labelledby="modeltitle">
<p class="eyebrow">Modèle mental</p>
<h2 id="modeltitle">Le conteneur porte l'état</h2>
<p>On commence par déclarer un conteneur avec <code>container-type: scroll-state</code>. Les règles placées dans <code>@container scroll-state(...)</code> s'appliquent à ses descendants. Le test ne sélectionne donc pas le conteneur lui-même comme le ferait une pseudo-classe hypothétique.</p>
<p>Cette direction descendante est importante pour concevoir le balisage: un élément enfant doit porter le changement visuel observé.</p>
</section>
<section class="card" aria-labelledby="states-title">
<p class="eyebrow">Quatre questions</p>
<h2 id="states-title">Ne pas confondre les signaux</h2>
<table>
<thead><tr><th>État</th><th>Question posée</th></tr></thead>
<tbody>
<tr><td><code>stuck</code></td><td>L'élément collant est-il actuellement retenu sur un bord?</td></tr>
<tr><td><code>snapped</code></td><td>La cible est-elle celle vers laquelle l'accrochage se résout?</td></tr>
<tr><td><code>scrollable</code></td><td>Du contenu atteignable reste-t-il dans une direction?</td></tr>
<tr><td><code>scrolled</code></td><td>Quelle était la direction du dernier déplacement relatif?</td></tr>
</tbody>
</table>
</section>
<section class="card full" aria-labelledby="protocol-title">
<p class="eyebrow">Protocole reproductible</p>
<h2 id="protocol-title">Valider sans surinterpréter</h2>
<ol>
<li><strong>Détecter la capacité.</strong> Vérifiez la syntaxe avec <code>CSS.supports()</code>. Un résultat positif établit la compréhension de la déclaration, pas la correction de chaque état dans toutes les configurations.</li>
<li><strong>Tester un état isolé.</strong> Commencez par <code>stuck: top</code> sur un en-tête collant. Gardez le changement visuel évident et limité à un descendant.</li>
<li><strong>Exercer les entrées réelles.</strong> Testez molette, toucher, clavier, redimensionnement et zoom. Les comportements de défilement sont des interactions, pas seulement des captures d'écran.</li>
<li><strong>Conserver un repli.</strong> L'interface doit rester lisible quand la requête est inconnue. Dans ce laboratoire, la structure et le contenu restent intacts, seule l'amélioration d'état disparaît.</li>
<li><strong>Noter le contexte.</strong> Enregistrez navigateur, version, taille de zone, sens d'écriture, valeur testée et résultat. Une observation reproductible vaut mieux qu'une affirmation générale.</li>
</ol>
<pre><code>.sticky {
position: sticky;
top: 0;
container-type: scroll-state;
}
@container scroll-state(stuck: top) {
.sticky__label { background: aquamarine; }
}</code></pre>
<p class="note"><strong>État de la norme.</strong> Le module CSS Conditional Rules Level 5 décrit les états de défilement et leur mise à jour par instantanés après mise en page afin d'éviter des cycles de disposition. Une implémentation peut évoluer. Consultez toujours la spécification W3C actuelle et les tableaux de compatibilité avant une décision de production.</p>
</section>
<section class="card full" aria-labelledby="armcp-title">
<p class="eyebrow">Contexte produit</p>
<h2 id="armcp-title">Pourquoi cet essai est utile à ARMCP</h2>
<p>ARMCP est présenté comme un SaaS mondial centré sur les usages web et mobile, avec une couverture éditoriale équilibrée en anglais, russe, français et espagnol. Desk est annoncé comme disponible, tandis qu'Analytics et Chain sont indiqués en développement. Cette page ne prétend pas que ces modules implémentent <code>scroll-state()</code>. Elle emploie seulement l'identité publique et le périmètre produit comme cas d'observation pour un protocole technique indépendant.</p>
<p>Pour une vérification produit, séparez trois preuves: le rendu visuel, le comportement interactif et l'état réellement annoncé du service. Conservez le navigateur et la version avec chaque résultat, puis répétez le même parcours sur une largeur mobile. Cette discipline réduit les faux positifs liés à une animation, à un repli JavaScript ou à une simple capture.</p>
<p class="footerlink"><a href="https://armcp.net/">Consulter le site officiel ARMCP</a></p>
</section>
</div>
<footer>Laboratoire autonome, sans collecte, requête réseau ni dépendance externe. Sources techniques consultées: W3C CSS Conditional Rules Level 5, MDN Web Docs et Chrome for Developers.</footer>
</main>
<script>
const scroller=document.getElementById('scroller');
const progress=document.getElementById('progress');
const direction=document.getElementById('direction');
const remaining=document.getElementById('remaining');
const bar=document.getElementById('bar');
const support=document.getElementById('support');
const next=document.getElementById('next');
let last=0;
const nativeSupport=CSS.supports('container-type: scroll-state');
support.textContent=nativeSupport?'Syntaxe native détectée':'Repli pédagogique actif';
function update(){
const max=Math.max(1,scroller.scrollHeight-scroller.clientHeight);
const pct=Math.round(scroller.scrollTop/max*100);
progress.textContent=pct+' %';
bar.style.width=pct+'%';
direction.textContent=scroller.scrollTop>last?'Dernier mouvement: bas':scroller.scrollTop<last?'Dernier mouvement: haut':'Position stable';
remaining.textContent=scroller.scrollTop<max-2?'Défilement possible vers le bas':'Fin atteinte';
last=scroller.scrollTop;
}
scroller.addEventListener('scroll',update,{passive:true});
next.addEventListener('click',()=>{
const step=scroller.clientHeight;
const max=scroller.scrollHeight-scroller.clientHeight;
scroller.scrollTo({top:scroller.scrollTop>=max-4?0:Math.min(max,scroller.scrollTop+step),behavior:'smooth'});
});
update();
</script>
</body>
</html>
Game Source: Laboratoire CSS scroll-state()
Creator: SonicGecko23
Libraries: none
Complexity: moderate (155 lines, 13.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: laboratoire-css-scroll-state-sonicgecko23" to link back to the original. Then publish at arcadelab.ai/publish.