Changeset e46ebaa in Klonkt for src/assets/js/mod/read.js
- Timestamp:
- 08/20/2026 12:54:30 AM (3 weeks ago)
- Branches:
- main
- Children:
- 48481cd
- Parents:
- 598f090
- File:
-
- 1 edited
-
src/assets/js/mod/read.js (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/mod/read.js
r598f090 re46ebaa 17 17 * cmd/ctrl-klik hoort de browser zelf af te handelen. 18 18 */ 19 20 /** 21 * Snappen alleen waar het HELPT. 22 * 23 * Elk bericht heeft scroll-snap-align:start en min-height:100svh. Voor een kort 24 * bericht is dat precies goed: het vult het scherm en de volgende klikt netjes 25 * op zijn plek. Voor een LANG bericht is het een val -- je leest naar beneden en 26 * de browser trekt je terug naar de bovenrand, waardoor het scrollen lijkt te 27 * stoppen terwijl je gewoon doorscrollt. Barts melding (20-8). 28 * 29 * "Past dit bericht op het scherm?" is een vraag over gemeten hoogte, en die kan 30 * CSS niet stellen -- vandaar hier. Past het niet, dan gaat de snap eraf en lees 31 * je ononderbroken door tot het volgende bericht wel weer een snappunt is. 32 * 33 * De marge van 4px vangt afrondingsverschillen tussen svh en de echte hoogte; 34 * zonder die speling wipt een bericht dat toevallig exact past heen en weer. 35 */ 36 /** 37 * Wanneer is de bovenkant van een bericht een snappunt? 38 * 39 * Alleen zolang je er nog NIET voorbij bent. Zit de bovenrand op of onder de 40 * bovenkant van het scherm, dan kom je er nog aan en mag hij vangen. Is hij 41 * eenmaal voorbij -- je leest in het bericht -- dan gaat de snap eraf. 42 * 43 * Robins formulering (20-8), en die is preciezer dan wat ik er eerst van maakte: 44 * "in de post zelf mag er nooit gesnapt worden, ook niet bovenin; enkel de 45 * onderkant mag snappen naar de volgende post, dus daar de bovenkant van". 46 * Precies dat: de onderkant van bericht N is de bovenkant van N+1, en die is een 47 * doelwit omdat je hem nadert. De bovenkant van het bericht waar je IN zit is 48 * dat niet meer, want daar ben je voorbij. 49 * 50 * Zonder deze regel trok proximity je terug naar de bovenrand zodra je een paar 51 * regels verder scrolde, en leek het of het scrollen vastliep. 52 * 53 * De 4px is speling voor afronding: tijdens het snappen zelf loopt top naar 0 en 54 * mag hij niet halverwege afhaken. 55 */ 56 const SPELING = 4; 57 58 /** 59 * En snappen doet alleen mee op de weg NAAR BENEDEN (Robin, 20-8). 60 * 61 * Omhoog scroll je om iets terug te zoeken, en dan is elke vangst een hindernis: 62 * je wilt zelf bepalen waar je stopt. Omlaag lees je door, en dan helpt de 63 * grens juist. Dus bij omhoog gaat de snap er overal af. 64 * 65 * De drempel van 2px is er tegen richtingsruis: tijdens een vloeiende snap 66 * schommelt scrollY een fractie, en zonder speling zou de richting dan heen en 67 * weer klappen -- precies midden in de beweging die net soepel moest zijn. 68 */ 69 let laatsteY = 0; 70 let omlaag = true; 71 72 /** 73 * Hoe DICHT bij de grens hij vangt: de onderste VANGZONE pixels van een bericht. 74 * 75 * In pixels, en bewust niet inhoudelijk. Ik probeerde het aan de voet van het 76 * bericht te hangen ("ben je de reacties voorbij"), maar bij een KORT bericht 77 * gaat dat mis: min-height rekt zo'n bericht tot een vol scherm, dus de voet 78 * staat middenin met lege ruimte eronder en de zone begint op een willekeurige 79 * plek. Robin ving dat (20-8) voordat het uitgerold stond. 80 * 81 * Eerst stond dit op 20px en dat was te krap om ooit te vangen. Honderdvijftig 82 * is ruwweg het laatste stukje van een bericht: kom je daarbinnen tot stilstand, 83 * dan trekt hij de streep recht naar de bovenkant van het volgende. Stop je 84 * eerder, dan blijf je gewoon staan. 85 * 86 * Eén getal, dus makkelijk bij te stellen als het onder een duim anders voelt. 87 */ 88 const VANGZONE = 150; 89 90 function ijkEen(art) { 91 const top = art.getBoundingClientRect().top; 92 // Omhoog: nooit. Omlaag: alleen een grens die je NADERT en die binnen de zone 93 // ligt. De ondergrens blijft krap (SPELING): eenmaal voorbij wordt er niet 94 // teruggetrokken, ook niet over een paar pixels. 95 const wil = (!omlaag || top < -SPELING || top > VANGZONE) ? 'none' : ''; 96 // Alleen aanraken als het echt verandert: elke stijlwijziging tijdens een 97 // vloeiende scroll is een kans op een hik, zeker op iOS. 98 if (art.style.scrollSnapAlign !== wil) art.style.scrollSnapAlign = wil; 99 } 100 101 function ijkSnappen() { 102 const y = window.scrollY; 103 if (Math.abs(y - laatsteY) > 2) omlaag = y > laatsteY; 104 laatsteY = y; 105 document.querySelectorAll('.feed-reader .read-post').forEach(ijkEen); 106 } 107 108 /** 109 * Terug naar boven, en bewust NIET window.scrollTo(0). 110 * 111 * In een stroom wil je terug naar het BEGIN VAN DIT BERICHT als je halverwege een 112 * lang stuk zit, en pas daarna naar de kop van de pagina. Twee keer drukken doet 113 * dus twee verschillende dingen -- dat scheelt op mobiel een halve minuut vegen. 114 */ 115 function naarBoven() { 116 const zacht = !window.matchMedia('(prefers-reduced-motion: reduce)').matches; 117 const gedrag = zacht ? 'smooth' : 'auto'; 118 const posts = [...document.querySelectorAll('.feed-reader .read-post')]; 119 const huidig = posts.find((a) => { 120 const r = a.getBoundingClientRect(); 121 return r.top <= 8 && r.bottom > 8; 122 }); 123 // Sta je al bovenaan dit bericht (of bij het eerste), dan naar de paginakop. 124 if (huidig && huidig.getBoundingClientRect().top < -8) { 125 huidig.scrollIntoView({ behavior: gedrag, block: 'start' }); 126 return; 127 } 128 window.scrollTo({ top: 0, behavior: gedrag }); 129 } 130 131 /** De knop verschijnt pas als er iets ONDER je ligt om naar terug te keren. */ 132 function toonKnop(knop) { 133 knop.classList.toggle('is-zichtbaar', window.scrollY > window.innerHeight * 0.6); 134 } 19 135 20 136 let tapX = 0, tapY = 0; … … 37 153 } 38 154 155 let knop = null; 156 let opScroll = null; 157 39 158 export function init() { 40 159 const s = document.getElementById('read-stream'); … … 47 166 s.addEventListener('pointerdown', onPointerDown, { passive: true }); 48 167 s.addEventListener('click', onTap); 168 169 // De knop staat in de HTML (read-top.ejs), zodat hij er ook is zonder deze 170 // module -- dan doet hij niets, maar hij springt niet in beeld bij het laden. 171 knop = document.getElementById('read-top'); 172 if (knop) { 173 knop.onclick = naarBoven; 174 toonKnop(knop); 175 } 176 177 // Eén luisteraar, en losmaken bij een volgende init(): deze module draait bij 178 // ELKE paginawissel en anders stapelen ze op. 179 if (opScroll) { 180 window.removeEventListener('scroll', opScroll); 181 window.removeEventListener('resize', opScroll); 182 } 183 // Eén keer per frame, niet per scroll-event: scroll vuurt tientallen keren per 184 // seconde en we raken hier de stijl van elk bericht aan. 185 let gepland = false; 186 opScroll = () => { 187 if (knop) toonKnop(knop); 188 if (gepland) return; 189 gepland = true; 190 requestAnimationFrame(() => { gepland = false; ijkSnappen(); }); 191 }; 192 window.addEventListener('scroll', opScroll, { passive: true }); 193 window.addEventListener('resize', opScroll, { passive: true }); 194 ijkSnappen(); // ook meteen bij binnenkomst, niet pas bij de eerste scroll 49 195 } 50 196
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)