| 1 | /**
|
|---|
| 2 | * De leesweergave: tikken op een bericht opent dat bericht, en een knop om terug
|
|---|
| 3 | * naar boven te gaan.
|
|---|
| 4 | *
|
|---|
| 5 | * Dit was een heel scherm met een eigen route, dat zijn buren zelf ophaalde, de
|
|---|
| 6 | * scrollpositie corrigeerde bij invoegen en de balken wegschoof. Dat is allemaal
|
|---|
| 7 | * weg, en dat is winst: Lezen is nu een WEERGAVE van de feed
|
|---|
| 8 | * (body[data-feed-view="reader"]), naast Tijdlijn en Grid. De feed levert de
|
|---|
| 9 | * berichten al, "meer laden" vult al aan, en het snappen naar berichtgrenzen
|
|---|
| 10 | * doet CSS.
|
|---|
| 11 | *
|
|---|
| 12 | * HET SNAPPEN STAAT HIER MET OPZET NIET IN. Ik heb dat een ronde lang wel
|
|---|
| 13 | * geprobeerd -- richting bijhouden, een vangzone uitrekenen, per scroll-event
|
|---|
| 14 | * het snappunt verzetten -- en dat is de verkeerde laag. Robins bezwaar (20-8):
|
|---|
| 15 | * "scroll-snap op het element is iets anders dan touch events,
|
|---|
| 16 | * requestAnimationFrame etc. dan gaan we te veel van de view doen." Klopt, en
|
|---|
| 17 | * het vocht ook met de browser: de scroll-events bevatten OOK de bewegingen van
|
|---|
| 18 | * zijn eigen snap-animatie, dus de richting die je eruit afleidt is niet die van
|
|---|
| 19 | * de gebruiker.
|
|---|
| 20 | *
|
|---|
| 21 | * Wat er nodig was, was een regel minder in de CSS en niet honderd erbij hier:
|
|---|
| 22 | * zie style.css bij .feed-reader .read-post.
|
|---|
| 23 | *
|
|---|
| 24 | * De titel en de voetlink in read-article.ejs zijn echte <a>'s en doen het werk
|
|---|
| 25 | * voor toetsenbord en schermlezer; de tik hieronder is er voor een duim.
|
|---|
| 26 | *
|
|---|
| 27 | * Vier uitzonderingen op die tik, want een tik die je niet bedoelde is erger dan
|
|---|
| 28 | * geen tik: iets dat zelf al een doel heeft (link, knop, veld) houdt zijn eigen
|
|---|
| 29 | * werking, een geselecteerde tekst is geen tik, een verschoven vinger is
|
|---|
| 30 | * scrollen, en cmd/ctrl-klik hoort de browser zelf af te handelen.
|
|---|
| 31 | */
|
|---|
| 32 |
|
|---|
| 33 | // ── Paginamodus ─────────────────────────────────────────────────────────────
|
|---|
| 34 | /**
|
|---|
| 35 | * Elk bericht een paneel dat zelf scrollt; tussen berichten ga je met knoppen.
|
|---|
| 36 | *
|
|---|
| 37 | * Robins voorstel (20-8) nadat het namaken van iOS-momentum niet lukte, en dat
|
|---|
| 38 | * is de winst: in dit model DOET het scrollgevoel er niet toe. Er is geen
|
|---|
| 39 | * momentum om na te bouwen en geen snap die vangt.
|
|---|
| 40 | *
|
|---|
| 41 | * Hoe het werkt:
|
|---|
| 42 | * - lenis.stop() zet het scrollen van de STROOM stil. Lenis vangt dan wiel en
|
|---|
| 43 | * vinger af (hij preventDefault't in onVirtualScroll), dus je kunt niet meer
|
|---|
| 44 | * tussen berichten door scrollen.
|
|---|
| 45 | * - data-lenis-prevent op elk paneel houdt de scroll BINNEN een bericht
|
|---|
| 46 | * ongemoeid. Dat werkt ook terwijl Lenis gestopt is, want die controle staat
|
|---|
| 47 | * in zijn bron vóór de gestopt-controle. Nagekeken in de dist, niet gehoopt.
|
|---|
| 48 | * - Bewegen tussen panelen gaat met lenis.scrollTo(..., { force: true }), want
|
|---|
| 49 | * force is precies de uitzondering die een gestopte Lenis toch laat scrollen.
|
|---|
| 50 | *
|
|---|
| 51 | * Geldt waar een bericht een eigen scherm heeft: op mobiel altijd, op desktop
|
|---|
| 52 | * als de site dat instelt (reader_full_page). Buiten die modus verandert er
|
|---|
| 53 | * niets -- dan is Lenis gewoon aan en scrol je vrij.
|
|---|
| 54 | */
|
|---|
| 55 | /**
|
|---|
| 56 | * PAGINAMODUS STAAT UIT -- maar de code blijft staan (Robin, 20-8: "weghalen
|
|---|
| 57 | * maar bewaren, voor als ik het later weer wil").
|
|---|
| 58 | *
|
|---|
| 59 | * Wat het is: elk bericht een paneel van een scherm dat zelf scrolt, met twee
|
|---|
| 60 | * balken om ertussen te navigeren en de stroomscroll vastgezet. Gebouwd omdat
|
|---|
| 61 | * het namaken van iOS-momentum niet lukte; in dit model doet het scrollgevoel er
|
|---|
| 62 | * namelijk niet toe. Mobiel is nu terug op systeemscroll met snap, en dat is wat
|
|---|
| 63 | * er ook stond voordat we dit probeerden.
|
|---|
| 64 | *
|
|---|
| 65 | * Aanzetten: deze constante op true. Dan komen de balken terug (CSS hangt aan
|
|---|
| 66 | * body.is-paged), stopt Lenis de stroomscroll en scrollen de panelen zelf.
|
|---|
| 67 | */
|
|---|
| 68 | const PAGINAMODUS = false;
|
|---|
| 69 |
|
|---|
| 70 | function paginaModus() {
|
|---|
| 71 | return PAGINAMODUS && OP_TOUCH.matches && document.body.dataset.feedView === 'reader';
|
|---|
| 72 | }
|
|---|
| 73 |
|
|---|
| 74 | function panelen() {
|
|---|
| 75 | return [...document.querySelectorAll('.feed-reader .read-post')];
|
|---|
| 76 | }
|
|---|
| 77 |
|
|---|
| 78 | /** Welk paneel vult nu het scherm? Het eerste waarvan de bovenkant niet voorbij is. */
|
|---|
| 79 | function huidigIndex() {
|
|---|
| 80 | const P = panelen();
|
|---|
| 81 | for (let i = 0; i < P.length; i++) {
|
|---|
| 82 | if (P[i].getBoundingClientRect().top > 8) return Math.max(0, i - 1);
|
|---|
| 83 | }
|
|---|
| 84 | return Math.max(0, P.length - 1);
|
|---|
| 85 | }
|
|---|
| 86 |
|
|---|
| 87 | function gaNaar(i) {
|
|---|
| 88 | const P = panelen();
|
|---|
| 89 | const doel = P[Math.max(0, Math.min(P.length - 1, i))];
|
|---|
| 90 | if (!doel) return;
|
|---|
| 91 | if (lenis) lenis.scrollTo(doel, { force: true });
|
|---|
| 92 | else doel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|---|
| 93 | setTimeout(zetBalken, 80);
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | /**
|
|---|
| 97 | * Omhoog vanaf het EERSTE bericht brengt je naar de header.
|
|---|
| 98 | *
|
|---|
| 99 | * In paginamodus staat Lenis stil, dus de pagina scrolt niet meer met je vinger
|
|---|
| 100 | * -- en dan is alles boven het eerste bericht onbereikbaar. Robin liep daar
|
|---|
| 101 | * tegenaan (20-8): "ik kan niet meer terug scrollen naar de header". De
|
|---|
| 102 | * omhoog-knop is daar de enige weg naartoe, dus die krijgt er een trede bij.
|
|---|
| 103 | */
|
|---|
| 104 | function gaOmhoog() {
|
|---|
| 105 | const i = huidigIndex();
|
|---|
| 106 | if (i > 0) { gaNaar(i - 1); return; }
|
|---|
| 107 | if (lenis) lenis.scrollTo(0, { force: true });
|
|---|
| 108 | else window.scrollTo({ top: 0, behavior: 'smooth' });
|
|---|
| 109 | }
|
|---|
| 110 |
|
|---|
| 111 | /**
|
|---|
| 112 | * Welke balk hoort er te staan? Bovenaan de pagina is er niets boven je, dus dan
|
|---|
| 113 | * geen bovenbalk. Buiten paginamodus staan ze allebei niet -- de CSS verbergt ze
|
|---|
| 114 | * daar al, maar hidden houdt ze ook uit de toetsenbordvolgorde.
|
|---|
| 115 | */
|
|---|
| 116 | function zetBalken() {
|
|---|
| 117 | const boven = document.getElementById('read-prev');
|
|---|
| 118 | const onder = document.getElementById('read-next-nav');
|
|---|
| 119 | const aan = paginaModus();
|
|---|
| 120 | // De bovenbalk hoort NOOIT over de header te liggen. Hij verscheen al zodra je
|
|---|
| 121 | // een paar pixels scrolde, en dekte dan de avatar, de omschrijving en de
|
|---|
| 122 | // weergaveknoppen af (Robins schermafbeelding, 20-8). Nu komt hij pas als het
|
|---|
| 123 | // eerste bericht de bovenrand van het scherm heeft bereikt -- dan is de header
|
|---|
| 124 | // voorbij en is er ook echt iets om naar terug te gaan.
|
|---|
| 125 | const eerste = panelen()[0];
|
|---|
| 126 | const headerNogInBeeld = eerste ? eerste.getBoundingClientRect().top > 4 : true;
|
|---|
| 127 | if (boven) boven.hidden = !aan || headerNogInBeeld;
|
|---|
| 128 | if (onder) onder.hidden = !aan;
|
|---|
| 129 | if (onder && aan) onder.style.bottom = onderChroom() + 'px';
|
|---|
| 130 | }
|
|---|
| 131 |
|
|---|
| 132 | /**
|
|---|
| 133 | * Hoe hoog staat de onderrand van het scherm werkelijk vol?
|
|---|
| 134 | *
|
|---|
| 135 | * De onderbalk stond op een geraden 4.75rem boven de onderkant, en dan zweeft
|
|---|
| 136 | * hij: soms een kier boven de speler, soms er half achter. De tabbalk en de
|
|---|
| 137 | * mini-speler hebben allebei een eigen hoogte, ze stapelen op mobiel, en de
|
|---|
| 138 | * speler komt en gaat. Dus meten in plaats van gokken: hoe ver ligt de BOVENKANT
|
|---|
| 139 | * van het hoogste vaste element boven de onderrand van het venster?
|
|---|
| 140 | */
|
|---|
| 141 | function onderChroom() {
|
|---|
| 142 | // .bottom-tab-fab staat erbij omdat de Write-knop BOVEN de tabbalk uitsteekt:
|
|---|
| 143 | // meet je alleen de balk, dan legt onze balk zich over die knop heen (Robins
|
|---|
| 144 | // schermafbeelding, 20-8).
|
|---|
| 145 | const kandidaten = ['.bottom-tab', '.bottom-tab-fab', '#pcms-audio-player'];
|
|---|
| 146 | let hoogste = 0;
|
|---|
| 147 | kandidaten.forEach((sel) => {
|
|---|
| 148 | const el = document.querySelector(sel);
|
|---|
| 149 | if (!el) return;
|
|---|
| 150 | const st = getComputedStyle(el);
|
|---|
| 151 | if (st.display === 'none' || st.visibility === 'hidden') return;
|
|---|
| 152 | const r = el.getBoundingClientRect();
|
|---|
| 153 | if (r.height <= 0) return;
|
|---|
| 154 | hoogste = Math.max(hoogste, window.innerHeight - r.top);
|
|---|
| 155 | });
|
|---|
| 156 | return Math.round(hoogste);
|
|---|
| 157 | }
|
|---|
| 158 |
|
|---|
| 159 | function pasPaginaModusToe() {
|
|---|
| 160 | const aan = paginaModus();
|
|---|
| 161 | document.body.classList.toggle('is-paged', aan);
|
|---|
| 162 | panelen().forEach((a) => {
|
|---|
| 163 | if (aan) a.setAttribute('data-lenis-prevent', '');
|
|---|
| 164 | else a.removeAttribute('data-lenis-prevent');
|
|---|
| 165 | });
|
|---|
| 166 | if (lenis) { if (aan) lenis.stop(); else lenis.start(); }
|
|---|
| 167 | zetBalken();
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | /**
|
|---|
| 171 | * Terug naar boven, en bewust NIET window.scrollTo(0).
|
|---|
| 172 | *
|
|---|
| 173 | * In een stroom wil je terug naar het BEGIN VAN DIT BERICHT als je halverwege een
|
|---|
| 174 | * lang stuk zit, en pas daarna naar de kop van de pagina. Twee keer drukken doet
|
|---|
| 175 | * dus twee verschillende dingen -- dat scheelt op mobiel een hoop vegen.
|
|---|
| 176 | */
|
|---|
| 177 | function naarBoven() {
|
|---|
| 178 | const zacht = !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|---|
| 179 | const gedrag = zacht ? 'smooth' : 'auto';
|
|---|
| 180 | const posts = [...document.querySelectorAll('.feed-reader .read-post')];
|
|---|
| 181 | const huidig = posts.find((a) => {
|
|---|
| 182 | const r = a.getBoundingClientRect();
|
|---|
| 183 | return r.top <= 8 && r.bottom > 8;
|
|---|
| 184 | });
|
|---|
| 185 | // Sta je al bovenaan dit bericht (of bij het eerste), dan naar de paginakop.
|
|---|
| 186 | if (huidig && huidig.getBoundingClientRect().top < -8) {
|
|---|
| 187 | huidig.scrollIntoView({ behavior: gedrag, block: 'start' });
|
|---|
| 188 | return;
|
|---|
| 189 | }
|
|---|
| 190 | window.scrollTo({ top: 0, behavior: gedrag });
|
|---|
| 191 | }
|
|---|
| 192 |
|
|---|
| 193 | /** De knop verschijnt pas als er iets ONDER je ligt om naar terug te keren. */
|
|---|
| 194 | function toonKnop(knop) {
|
|---|
| 195 | knop.classList.toggle('is-zichtbaar', window.scrollY > window.innerHeight * 0.6);
|
|---|
| 196 | }
|
|---|
| 197 |
|
|---|
| 198 | let tapX = 0, tapY = 0;
|
|---|
| 199 | function onPointerDown(e) { tapX = e.clientX; tapY = e.clientY; }
|
|---|
| 200 |
|
|---|
| 201 | function onTap(e) {
|
|---|
| 202 | if (e.defaultPrevented || e.button !== 0) return;
|
|---|
| 203 | if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|---|
| 204 | const t = e.target;
|
|---|
| 205 | if (!t || typeof t.closest !== 'function') return;
|
|---|
| 206 | const art = t.closest('.read-post');
|
|---|
| 207 | if (!art) return;
|
|---|
| 208 | if (t.closest('a, button, input, textarea, select, label, summary, [role="button"]')) return;
|
|---|
| 209 | if (Math.abs(e.clientX - tapX) > 10 || Math.abs(e.clientY - tapY) > 10) return;
|
|---|
| 210 | const sel = window.getSelection && window.getSelection();
|
|---|
| 211 | if (sel && String(sel).trim()) return;
|
|---|
| 212 |
|
|---|
| 213 | const slug = art.dataset.slug;
|
|---|
| 214 | if (!slug) return;
|
|---|
| 215 | location.href = (art.dataset.base || '') + '/' + encodeURIComponent(slug);
|
|---|
| 216 | }
|
|---|
| 217 |
|
|---|
| 218 | /**
|
|---|
| 219 | * Omhoog scrollen blijft VRIJ (Robin, 20-8).
|
|---|
| 220 | *
|
|---|
| 221 | * Snappen hoort bij doorlezen; ga je terug, dan zoek je iets en bepaal je zelf
|
|---|
| 222 | * waar je stopt. CSS kent geen richtingsgevoelig snappen, dus dat ene stukje
|
|---|
| 223 | * moet hier -- maar dan ook niet meer dan dat: we zetten de CSS-functie aan of
|
|---|
| 224 | * uit op de scroller. Geen vangzones, geen snappunten per bericht, geen
|
|---|
| 225 | * scrollpositie-boekhouding.
|
|---|
| 226 | *
|
|---|
| 227 | * DE RICHTING KOMT UIT DE INVOER, niet uit scroll-events. Gemeten op dev: die
|
|---|
| 228 | * events bevatten ook de bewegingen van de browser zelf -- zijn snap-animatie en
|
|---|
| 229 | * de rubber-band -- en die gaan soms omhoog. Wie daaruit de richting afleidt,
|
|---|
| 230 | * leest de browser en niet de gebruiker.
|
|---|
| 231 | *
|
|---|
| 232 | * EN ER WORDT ALLEEN GESCHAKELD BIJ STILSTAND. Dat is de tweede les, en die
|
|---|
| 233 | * kostte een ronde. Eerst zette dit de schakelaar om bij ELK wiel-event en ELKE
|
|---|
| 234 | * vingerbeweging, dus binnen een veeg klapte hij meerdere keren heen en weer.
|
|---|
| 235 | * De browser raadpleegt scroll-snap-type alleen aan het eind van een gebaar of
|
|---|
| 236 | * van de uitloop, en of je daar net vóór of net ná zit bepaalt dan of er
|
|---|
| 237 | * gesnapt wordt. Gemeten (Robins melding "soms triggert het terwijl we nog aan
|
|---|
| 238 | * het doorscrollen zijn"):
|
|---|
| 239 | *
|
|---|
| 240 | * snappen UIT, beweging naar 1459 loopt
|
|---|
| 241 | * -> halverwege snappen AAN gezet
|
|---|
| 242 | * -> eindigt op 1459, NIET op een berichtgrens
|
|---|
| 243 | *
|
|---|
| 244 | * Bij stilstand omzetten is wel onschuldig: dezelfde proef gaf 0px sprong.
|
|---|
| 245 | * Vandaar: richting bepalen bij het BEGIN van een gebaar, en daarna niets meer
|
|---|
| 246 | * aanraken tot de scroll echt stil is.
|
|---|
| 247 | *
|
|---|
| 248 | * Wat je daarvoor inlevert: binnen een veeg ligt de stand vast. Draai je
|
|---|
| 249 | * halverwege om zonder los te laten, dan geldt de stand van dat gebaar nog. Een
|
|---|
| 250 | * besluit per gebaar is voorspelbaar; het omklappen halverwege was het probleem.
|
|---|
| 251 | */
|
|---|
| 252 | const RUST_MS = 120;
|
|---|
| 253 |
|
|---|
| 254 | let bezig = false; // loopt er een gebaar of een uitloop?
|
|---|
| 255 | let ooitGescrold = false; // snappen begint UIT, zie zetSnappen()
|
|---|
| 256 | let rustTimer = null;
|
|---|
| 257 |
|
|---|
| 258 | /**
|
|---|
| 259 | * LENIS, en alleen op desktop.
|
|---|
| 260 | *
|
|---|
| 261 | * Op touch doet Lenis van zichzelf niets (syncTouch staat standaard uit) en is
|
|---|
| 262 | * het systeem-scrollen al soepel -- daar blijft de native CSS-snap staan die
|
|---|
| 263 | * hierboven beschreven is. Robins keuze (20-8): "enkel voor desktop, dat is
|
|---|
| 264 | * prima, logisch dat het niet op mobiel kan".
|
|---|
| 265 | *
|
|---|
| 266 | * WAAROM LENIS HIER STAAT, en dat is niet het vloeiende scrollen: de DUUR van
|
|---|
| 267 | * een snap is met native scroll-snap niet in te stellen -- die zit in de browser.
|
|---|
| 268 | * Lenis' snap-pakket wel: duration, easing, distanceThreshold en debounce zijn
|
|---|
| 269 | * allemaal van ons. Dat was de aanleiding.
|
|---|
| 270 | *
|
|---|
| 271 | * Het vloeiende scrollen (smoothWheel) kwam er daarna bij, en dat is de kant die
|
|---|
| 272 | * OPPASSEN vraagt. Een Mac-trackpad heeft zijn EIGEN momentum, en Lenis'
|
|---|
| 273 | * demping komt daar bovenop -- dubbel gedempt voelt drijverig. Vandaar lerp 0.2
|
|---|
| 274 | * in plaats van de standaard 0.1. Robin vond dat op 20-8 nog steeds te zweverig,
|
|---|
| 275 | * dus dat getal is nog niet uit; zie de opmerking bij de instellingen.
|
|---|
| 276 | *
|
|---|
| 277 | * De stand met `smoothWheel: false` werkte ook, en dan doet Lenis alleen de
|
|---|
| 278 | * snap. Dat is de terugvalpositie als het vloeiende scrollen niet bevalt.
|
|---|
| 279 | *
|
|---|
| 280 | * lenis/snap haakt alleen in op lenis.on('scroll') en roept lenis.scrollTo aan
|
|---|
| 281 | * (nagekeken in de dist), dus die opzet werkt.
|
|---|
| 282 | */
|
|---|
| 283 | const OP_DESKTOP = window.matchMedia('(hover: hover) and (pointer: fine)');
|
|---|
| 284 | // PROEF (20-8): Lenis ook op touch. Robin meldde op mobiel drie dingen -- de
|
|---|
| 285 | // header niet zichtbaar bij laden, snappen dat soms te ver gaat, en horizontaal
|
|---|
| 286 | // kunnen scrollen. De eerste twee komen van de NATIVE snap: die vangt al bij het
|
|---|
| 287 | // laden, en zijn uitloop vliegt bij een flick over een grens heen. Lenis zet de
|
|---|
| 288 | // native snap uit en doet het zelf, dus die twee kunnen ermee verdwijnen.
|
|---|
| 289 | //
|
|---|
| 290 | // De prijs is echt: syncTouch betekent dat Lenis het VINGERSCROLLEN overneemt
|
|---|
| 291 | // van iOS -- momentum, rubber-band en het wegschuiven van de adresbalk worden
|
|---|
| 292 | // dan een benadering in JavaScript. De makers waarschuwen daar zelf voor op
|
|---|
| 293 | // iOS < 16. Dit staat er dus als proef, niet als besluit: voelt het niet goed,
|
|---|
| 294 | // dan is OP_TOUCH weer false zetten de hele terugweg.
|
|---|
| 295 | const OP_TOUCH = window.matchMedia('(hover: none) and (pointer: coarse)');
|
|---|
| 296 | const VENDOR_V = 1; // ophogen als de bestanden in /assets/js/vendor wijzigen
|
|---|
| 297 |
|
|---|
| 298 | let lenis = null;
|
|---|
| 299 | let snap = null;
|
|---|
| 300 |
|
|---|
| 301 | async function startLenis() {
|
|---|
| 302 | // DESKTOP EN TOUCH ALLEBEI. Er heeft hier een tijd gestaan dat mobiel terug was
|
|---|
| 303 | // op de systeemscroll; dat klopte niet meer met de regel hieronder, die
|
|---|
| 304 | // syncTouch juist AANzet op touch. Reden dat Lenis ook op de telefoon meedraait:
|
|---|
| 305 | // zijn snap heeft zijn scroll-gebeurtenissen nodig om te kunnen timen.
|
|---|
| 306 | if (lenis || !(OP_DESKTOP.matches || OP_TOUCH.matches)) return;
|
|---|
| 307 | const [L, S] = await Promise.all([
|
|---|
| 308 | import(`/assets/vendor/lenis.mjs?v=${VENDOR_V}`),
|
|---|
| 309 | import(`/assets/vendor/lenis-snap.mjs?v=${VENDOR_V}`),
|
|---|
| 310 | ]);
|
|---|
| 311 | lenis = new L.default({
|
|---|
| 312 | /**
|
|---|
| 313 | * Wat Lenis MET RUST LAAT.
|
|---|
| 314 | *
|
|---|
| 315 | * In paginamodus staat Lenis stil en scrolt alleen het paneel van het
|
|---|
| 316 | * bericht zelf. Dat regelde ik eerst met een data-lenis-prevent-attribuut
|
|---|
| 317 | * dat de module op elk paneel zette -- maar dan hangt het scrollen af van of
|
|---|
| 318 | * dat attribuut op tijd en op elk (ook later bijgeladen) paneel staat, en
|
|---|
| 319 | * Robin kon binnen een lang bericht niet scrollen.
|
|---|
| 320 | *
|
|---|
| 321 | * Deze functie stelt dezelfde vraag zonder die afhankelijkheid: is dit een
|
|---|
| 322 | * bericht-paneel? Dan bemoeit Lenis zich er niet mee en scrolt de browser
|
|---|
| 323 | * het zelf -- wat op een telefoon precies is wat je wilt, want dat is de
|
|---|
| 324 | * systeemscroll.
|
|---|
| 325 | */
|
|---|
| 326 | prevent: (node) => !!(node && node.classList
|
|---|
| 327 | && node.classList.contains('read-post')
|
|---|
| 328 | && document.body.classList.contains('is-paged')),
|
|---|
| 329 | // Lenis tekent de scrollbeweging zelf, maar STEVIG GEDEMPT (lerp 0.2 in
|
|---|
| 330 | // plaats van de standaard 0.1). Reden: een muiswiel scrollt in schokken en
|
|---|
| 331 | // heeft die demping nodig; een Mac-trackpad heeft zijn EIGEN momentum en
|
|---|
| 332 | // krijgt er dan een tweede overheen -- dat is precies het drijverige gevoel
|
|---|
| 333 | // waar Robin voor waarschuwde. Hoger betekent korter naijlen, dus dit is de
|
|---|
| 334 | // middenweg: de schokjes weg, de nasleep kort.
|
|---|
| 335 | // Staat het toch te zweven, dan is lerp omhoog (richting 1) of terug naar
|
|---|
| 336 | // smoothWheel:false de knop -- die stand werkte ook, met alleen de snap.
|
|---|
| 337 | smoothWheel: true,
|
|---|
| 338 | lerp: 0.2,
|
|---|
| 339 | // Alleen op touch overneemt hij het vingerscrollen; op desktop hoeft dat niet.
|
|---|
| 340 | syncTouch: OP_TOUCH.matches,
|
|---|
| 341 | // Twee touch-knoppen, en ze doen echt iets anders. Lenis rekent bij het
|
|---|
| 342 | // LOSLATEN eenmalig een doel uit -- afstand = snelheid^touchInertiaExponent
|
|---|
| 343 | // -- en kruipt daar dan naartoe met syncTouchLerp.
|
|---|
| 344 | //
|
|---|
| 345 | // touchInertiaExponent hoe VER de flick draagt (standaard 1.7)
|
|---|
| 346 | // syncTouchLerp hoe hard hij REMT (standaard 0.075)
|
|---|
| 347 | //
|
|---|
| 348 | // Let op: `touchInertiaMultiplier` bestaat NIET in 1.3.26. Lenis slikt
|
|---|
| 349 | // onbekende opties zonder fout, dus zo'n regel lijkt te werken en doet niets.
|
|---|
| 350 | // Controleer een optienaam in src/assets/vendor/lenis.mjs voor je hem zet.
|
|---|
| 351 | //
|
|---|
| 352 | // Robin wilde langer en sneller uitrollen zonder remgevoel, dus de exponent
|
|---|
| 353 | // gaat omhoog (een stevige flick draagt daarmee ruim vier tot zes keer zo
|
|---|
| 354 | // ver) en de lerp gaat juist boven de standaard: hij legt die afstand vlot
|
|---|
| 355 | // af in plaats van er stroperig naartoe te kruipen.
|
|---|
| 356 | ...(OP_TOUCH.matches ? { syncTouchLerp: 0.09, touchInertiaExponent: 2.2 } : {}),
|
|---|
| 357 | autoRaf: true,
|
|---|
| 358 | });
|
|---|
| 359 | snap = new S.default(lenis, {
|
|---|
| 360 | type: 'proximity',
|
|---|
| 361 | // DE VANGZONE, en dit is HET getal om aan te draaien. Ooit 12%, toen 55%
|
|---|
| 362 | // (20-8: eerder vangen, boven de reacties), en 20-8 terug naar 12% -- want
|
|---|
| 363 | // sinds het snappen alleen nog vooruit werkt, voelde die brede zone als
|
|---|
| 364 | // trekken tijdens het lezen. 12% is ~110 pixels op een venster van 910: hij
|
|---|
| 365 | // vangt pas vlak vóór de lijn.
|
|---|
| 366 | // Houd dit gelijk aan vangZone() hieronder: deze drempel bepaalt of
|
|---|
| 367 | // lenis/snap überhaupt aanklopt, die andere of wij het doorlaten. Zet je
|
|---|
| 368 | // deze lager dan die, dan komt onze regel nooit aan bod.
|
|---|
| 369 | distanceThreshold: '12%',
|
|---|
| 370 | // Hoe lang na de laatste scrollbeweging hij mag vangen. De standaard is 500
|
|---|
| 371 | // en dat voelt als te laat. Op touch nog korter dan op desktop, want daar
|
|---|
| 372 | // eindigt een veeg in een lange, trage staart -- en juist dan wil je dat de
|
|---|
| 373 | // snap er snel bij is in plaats van te wachten tot de laatste pixel stil ligt.
|
|---|
| 374 | //
|
|---|
| 375 | // LET OP DE SPANNING: een vloeiendere uitloop (lagere syncTouchLerp) maakt
|
|---|
| 376 | // die staart langer, en stelt de snap dus uit. Deze twee getallen houden
|
|---|
| 377 | // elkaar in evenwicht; draai je aan de een, kijk dan ook naar de ander.
|
|---|
| 378 | // OP TOUCH JUIST LANG, en dat is het omgekeerde van wat ik vorige ronde deed.
|
|---|
| 379 | // Tussen je vinger loslaten en het op gang komen van de uitloop zit een korte
|
|---|
| 380 | // stilte in de scrollbeweging. Met 25ms viel de snap precies in dat gat: hij
|
|---|
| 381 | // greep op het moment van loslaten en knipte de veeg af -- wat aanvoelt als
|
|---|
| 382 | // "stroef bij het loslaten", en het werd er dan ook erger van. 200ms laat de
|
|---|
| 383 | // uitloop eerst zijn werk doen.
|
|---|
| 384 | // Met een iOS-achtige uitloop duurt de staart lang; te kort wachten laat de
|
|---|
| 385 | // snap midden in de vlucht ingrijpen. 150ms laat hem uitrollen en vangt dan.
|
|---|
| 386 | debounce: OP_TOUCH.matches ? 150 : 60,
|
|---|
| 387 | // Op touch korter: een telefoon vraagt om directer antwoord dan een muis, en
|
|---|
| 388 | // de snap komt daar aan het eind van een lange uitloop -- dan mag hij kort.
|
|---|
| 389 | duration: OP_TOUCH.matches ? 0.18 : 0.4,
|
|---|
| 390 | // Vlot weg, dan steeds langzamer aankomen (Robin, 20-8). easeOutQuart: op de
|
|---|
| 391 | // helft van de tijd is 94% van de weg af, en de rest dempt zacht uit.
|
|---|
| 392 | // Bewust NIET Lenis' standaard easeOutExpo -- die schiet weg en kruipt dan
|
|---|
| 393 | // zo lang na dat het lijkt of hij niet afmaakt.
|
|---|
| 394 | easing: (t) => 1 - Math.pow(1 - t, 4),
|
|---|
| 395 | });
|
|---|
| 396 | // Het snappunt is de bovenkant van elk bericht -- ook van het laatste. Dat
|
|---|
| 397 | // deed eerder niet mee omdat zijn bovenkant onbereikbaar was; sinds het
|
|---|
| 398 | // laatste bericht in CSS een volle schermhoogte krijgt, kan hij wel.
|
|---|
| 399 | const berichten = [...document.querySelectorAll('.feed-reader .read-post')];
|
|---|
| 400 | berichten.forEach((a) => snap.addElement(a, { align: 'start' }));
|
|---|
| 401 |
|
|---|
| 402 | // ALLEEN VOORUIT VANGEN. Dit hoort bij de ruime zone hierboven en is niet
|
|---|
| 403 | // optioneel: lenis/snap kiest het DICHTSTBIJZIJNDE punt en zijn drempel geldt
|
|---|
| 404 | // naar twee kanten. Scroll je een lang bericht in en stop je 300 pixels onder
|
|---|
| 405 | // de bovenkant, dan is die bovenkant het dichtstbij -- en met een zone van 400
|
|---|
| 406 | // wordt je teruggetrokken. Dat is precies de klacht "ik kan niet scrollen
|
|---|
| 407 | // binnen een lang bericht", en met de oude zone van 86 pixels viel het alleen
|
|---|
| 408 | // niet op.
|
|---|
| 409 | //
|
|---|
| 410 | // Dus: een doel ACHTER je slaan we over. Ligt er een punt voor je binnen de
|
|---|
| 411 | // zone, dan gaan we daarheen; anders gebeurt er niets en scrol je vrij door.
|
|---|
| 412 | // Zo verruimt de zone alleen de kant waar hij bedoeld is.
|
|---|
| 413 | //
|
|---|
| 414 | // Waarom niet gewoon de drempel? Omdat lenis/snap alleen de dichtstbijzijnde
|
|---|
| 415 | // kandidaat beoordeelt: zonder deze omleiding houdt de zone vooruit op bij de
|
|---|
| 416 | // helft van de afstand tussen twee berichten, hoe groot je de drempel ook zet.
|
|---|
| 417 | //
|
|---|
| 418 | // DE REGEL, in Robins woorden (20-8): "enkel bij downscrollen, aan de
|
|---|
| 419 | // onderkant van elke post, snappen naar de lijn tussen de posts" -- en geen
|
|---|
| 420 | // snap op een bericht dat al voorbij is gescrold. Drie voorwaarden dus:
|
|---|
| 421 | // 1. de laatste echte beweging ging omlaag,
|
|---|
| 422 | // 2. het doel ligt VOOR je (een punt achter je slaan we over),
|
|---|
| 423 | // 3. het ligt binnen de vangzone -- die begint rond de voet van het bericht.
|
|---|
| 424 | // Het doel zelf blijft de bovenkant van het volgende bericht: dat IS de lijn
|
|---|
| 425 | // ertussen, en dat is het essentiële.
|
|---|
| 426 | //
|
|---|
| 427 | // DE ZONE IS EEN GETAL, geen '55%'. Die string kwam ongewijzigd uit de opties
|
|---|
| 428 | // en werd hier met een getal vergeleken -- altijd onwaar, dus deze terugval
|
|---|
| 429 | // heeft nooit gewerkt en alleen het doel dat lenis zelf koos kwam erdoor.
|
|---|
| 430 | const vangZone = () => 0.12 * window.innerHeight; // gelijk aan distanceThreshold hierboven
|
|---|
| 431 | // Lenis' eigen `direction` is het teken van de snelheid, en die is bij het
|
|---|
| 432 | // afvuren van de (gedebouncede) snap alweer nul. Daarom onthouden we de
|
|---|
| 433 | // laatste richting die er echt was.
|
|---|
| 434 | let laatsteRichtingOmlaag = true;
|
|---|
| 435 | lenis.on('scroll', () => {
|
|---|
| 436 | if (Math.abs(lenis.velocity) > 0.05) laatsteRichtingOmlaag = lenis.velocity > 0;
|
|---|
| 437 | });
|
|---|
| 438 | const echtGaNaar = snap.goTo.bind(snap);
|
|---|
| 439 | snap.goTo = (index) => {
|
|---|
| 440 | if (!laatsteRichtingOmlaag) return; // omhoog: nooit vangen
|
|---|
| 441 | const punten = snap.computeSnaps(); // zelfde volgorde als goTo intern gebruikt
|
|---|
| 442 | const nu = lenis.scroll;
|
|---|
| 443 | let vooruit = -1;
|
|---|
| 444 | punten.forEach((punt, i) => {
|
|---|
| 445 | if (punt.value > nu + 2 && (vooruit < 0 || punt.value < punten[vooruit].value)) vooruit = i;
|
|---|
| 446 | });
|
|---|
| 447 | if (vooruit < 0) return; // niets meer voor je: vrij uitscrollen
|
|---|
| 448 | if (punten[vooruit].value - nu <= vangZone()) echtGaNaar(vooruit);
|
|---|
| 449 | };
|
|---|
| 450 |
|
|---|
| 451 | // OP TOUCH DOET DE MODULE-EIGEN TRIGGER HET NIET, en dat is meetbaar in de
|
|---|
| 452 | // vendor-bron, geen vermoeden. Drie feiten op een rij:
|
|---|
| 453 | //
|
|---|
| 454 | // 1. lenis geeft 'virtual-scroll' door met de RUWE vingerdelta, VOOR hij
|
|---|
| 455 | // de uitloop uitrekent (teken * |snelheid|^touchInertiaExponent).
|
|---|
| 456 | // 2. lenis-snap negeert elke touchmove en beoordeelt dus EEN keer per
|
|---|
| 457 | // gebaar, op touchend, met `scroll + rawDelta` -- de plek waar je
|
|---|
| 458 | // vinger LOSLIET.
|
|---|
| 459 | // 3. de uitloop draagt daarna nog honderden pixels verder.
|
|---|
| 460 | //
|
|---|
| 461 | // De snapbeslissing valt dus op een positie die de scroll meteen verlaat.
|
|---|
| 462 | // Op desktop klopt dezelfde som wel: wieldelta's zijn klein en de
|
|---|
| 463 | // smoothWheel-uitloop is kort, dus voorspelling en landing liggen bijeen.
|
|---|
| 464 | // Vandaar: op touch de module-trigger eraf en zelf beoordelen tegen
|
|---|
| 465 | // lenis.targetScroll -- die wordt synchroon bij touchend gezet en IS het
|
|---|
| 466 | // exacte landingspunt van de uitloop.
|
|---|
| 467 | if (OP_TOUCH.matches) {
|
|---|
| 468 | lenis.off('virtual-scroll', snap.onSnapDebounced);
|
|---|
| 469 | let raakTimer = null;
|
|---|
| 470 | let vertrek = 0; // scrollpositie bij loslaten: punten daarachter zijn
|
|---|
| 471 | // de bovenkant van het HUIDIGE bericht, nooit grijpen
|
|---|
| 472 | lenis.on('virtual-scroll', (e) => {
|
|---|
| 473 | const soort = e.event && e.event.type;
|
|---|
| 474 | if (soort === 'touchstart' || soort === 'touchmove') { clearTimeout(raakTimer); return; }
|
|---|
| 475 | if (soort !== 'touchend') return;
|
|---|
| 476 | vertrek = lenis.scroll;
|
|---|
| 477 | clearTimeout(raakTimer);
|
|---|
| 478 | raakTimer = setTimeout(() => {
|
|---|
| 479 | if (!laatsteRichtingOmlaag) return;
|
|---|
| 480 | const doel = lenis.targetScroll; // de echte landing, niet de raakdelta
|
|---|
| 481 | const punten = snap.computeSnaps();
|
|---|
| 482 | // De EERSTE grens voorbij het loslaatpunt, niet de dichtstbijzijnde bij
|
|---|
| 483 | // de landing. Robins regel (24-8): wordt het target voorbijgescrolld,
|
|---|
| 484 | // dan landen we alsnog OP het target. De zone werkt dus alleen aan de
|
|---|
| 485 | // korte kant -- kom je er niet eens bij in de buurt, dan lees je gewoon
|
|---|
| 486 | // binnen een lang bericht en blijft alles vrij. Eroverheen, hoe ver
|
|---|
| 487 | // ook, betekent grijpen. Een harde veeg komt daarmee altijd precies
|
|---|
| 488 | // een bericht verder, nooit twee: dat is het bladeren.
|
|---|
| 489 | let eerste = -1;
|
|---|
| 490 | punten.forEach((punt, i) => {
|
|---|
| 491 | if (punt.value > vertrek + 2 && (eerste < 0 || punt.value < punten[eerste].value)) eerste = i;
|
|---|
| 492 | });
|
|---|
| 493 | if (eerste < 0) return; // niets meer voor je: vrij uitscrollen
|
|---|
| 494 | if (doel >= punten[eerste].value - vangZone()) echtGaNaar(eerste);
|
|---|
| 495 | }, 150);
|
|---|
| 496 | });
|
|---|
| 497 | }
|
|---|
| 498 |
|
|---|
| 499 | // Native snappen uit: twee mechanismen op dezelfde scroller vechten.
|
|---|
| 500 | document.documentElement.style.scrollSnapType = 'none';
|
|---|
| 501 | }
|
|---|
| 502 |
|
|---|
| 503 | function stopLenis() {
|
|---|
| 504 | if (snap) { snap.destroy(); snap = null; }
|
|---|
| 505 | if (lenis) { lenis.destroy(); lenis = null; }
|
|---|
| 506 | document.documentElement.style.scrollSnapType = '';
|
|---|
| 507 | }
|
|---|
| 508 |
|
|---|
| 509 | /**
|
|---|
| 510 | * Omhoog niet snappen. Met Lenis is dat snap.stop()/start(); zonder Lenis (dus
|
|---|
| 511 | * op touch) zetten we de CSS-eigenschap om, precies zoals hiervoor.
|
|---|
| 512 | */
|
|---|
| 513 | function zetSnappen(aan) {
|
|---|
| 514 | if (snap) { if (aan) snap.start(); else snap.stop(); return; }
|
|---|
| 515 | // Zonder Lenis (mobiel) zetten we de CSS-eigenschap om. Hij staat bij het
|
|---|
| 516 | // laden UIT -- dat is de standaardstand van deze variabele -- zodat de browser
|
|---|
| 517 | // niet meteen naar het eerste bericht springt en de header wegvalt. Pas je
|
|---|
| 518 | // eerste gebaar naar beneden zet hem aan.
|
|---|
| 519 | const el = document.documentElement;
|
|---|
| 520 | const wil = aan ? '' : 'none';
|
|---|
| 521 | if (el.style.scrollSnapType !== wil) el.style.scrollSnapType = wil;
|
|---|
| 522 | }
|
|---|
| 523 |
|
|---|
| 524 | /** Een richting geldt alleen als er NIETS beweegt. Anders negeren we hem. */
|
|---|
| 525 | function nieuwGebaar(naarBeneden) {
|
|---|
| 526 | if (bezig) return;
|
|---|
| 527 | bezig = true;
|
|---|
| 528 | zetSnappen(naarBeneden);
|
|---|
| 529 | }
|
|---|
| 530 |
|
|---|
| 531 | /**
|
|---|
| 532 | * Het gebaar is pas voorbij als de SCROLL stil is, niet als de vinger loslaat:
|
|---|
| 533 | * op iOS loopt de uitloop daarna nog door. `scrollend` zegt dat precies, maar
|
|---|
| 534 | * bestaat niet overal (Chrome 114+, Safari 17+) -- vandaar ook de timer.
|
|---|
| 535 | */
|
|---|
| 536 | function rustNu() { bezig = false; }
|
|---|
| 537 | function planRust() {
|
|---|
| 538 | clearTimeout(rustTimer);
|
|---|
| 539 | rustTimer = setTimeout(rustNu, RUST_MS);
|
|---|
| 540 | }
|
|---|
| 541 |
|
|---|
| 542 | function opWiel(e) { if (Math.abs(e.deltaY) > 1) nieuwGebaar(e.deltaY > 0); }
|
|---|
| 543 | let raakY = 0;
|
|---|
| 544 | function opRaakStart(e) { if (e.touches && e.touches[0]) raakY = e.touches[0].clientY; }
|
|---|
| 545 | function opRaakBeweeg(e) {
|
|---|
| 546 | if (!e.touches || !e.touches[0]) return;
|
|---|
| 547 | const y = e.touches[0].clientY;
|
|---|
| 548 | // Vinger omhoog = inhoud omlaag. Drie pixels speling tegen de trilling van een
|
|---|
| 549 | // duim die stilstaat.
|
|---|
| 550 | if (Math.abs(y - raakY) > 3) { nieuwGebaar(y < raakY); raakY = y; }
|
|---|
| 551 | }
|
|---|
| 552 | function opToets(e) {
|
|---|
| 553 | if (['ArrowDown', 'PageDown', 'End', ' ', 'Spacebar'].indexOf(e.key) >= 0) nieuwGebaar(true);
|
|---|
| 554 | else if (['ArrowUp', 'PageUp', 'Home'].indexOf(e.key) >= 0) nieuwGebaar(false);
|
|---|
| 555 | }
|
|---|
| 556 |
|
|---|
| 557 | let knop = null;
|
|---|
| 558 | let opScroll = null;
|
|---|
| 559 |
|
|---|
| 560 | export function init() {
|
|---|
| 561 | // EERST OPRUIMEN, en pas daarna terugvallen als er geen leesstroom is.
|
|---|
| 562 | //
|
|---|
| 563 | // Dit stond andersom, en dat brak het scrollen op de HELE site: navigeerde je
|
|---|
| 564 | // van Lezen naar het beheer, dan viel init() bij de ontbrekende leesstroom
|
|---|
| 565 | // meteen terug -- en bleef Lenis leven EN gestopt (lenis.stop() vangt wiel en
|
|---|
| 566 | // vinger af). Daarna scrolde niets meer, ook niet op pagina's die met Lezen
|
|---|
| 567 | // niets te maken hebben. Robins melding (20-8): "scrollen werkt nu nergens ook
|
|---|
| 568 | // niet in admin panels".
|
|---|
| 569 | //
|
|---|
| 570 | // Opruimen hoort dus bij het VERLATEN van de weergave, niet bij het opzetten
|
|---|
| 571 | // ervan. init() draait bij elke paginawissel, dus dit is de plek.
|
|---|
| 572 | stopLenis();
|
|---|
| 573 | document.body.classList.remove('is-paged');
|
|---|
| 574 | document.querySelectorAll('[data-lenis-prevent].read-post')
|
|---|
| 575 | .forEach((a) => a.removeAttribute('data-lenis-prevent'));
|
|---|
| 576 |
|
|---|
| 577 | const s = document.getElementById('read-stream');
|
|---|
| 578 | if (!s) return;
|
|---|
| 579 | // Op de stroom, niet per artikel: wat "meer laden" erbij zet doet vanzelf mee.
|
|---|
| 580 | // init() draait bij ELKE paginawissel, dus eerst losmaken -- anders stapelt
|
|---|
| 581 | // dezelfde afhandelaar zich op en vuurt hij twee keer.
|
|---|
| 582 | s.removeEventListener('pointerdown', onPointerDown);
|
|---|
| 583 | s.removeEventListener('click', onTap);
|
|---|
| 584 | s.addEventListener('pointerdown', onPointerDown, { passive: true });
|
|---|
| 585 | s.addEventListener('click', onTap);
|
|---|
| 586 |
|
|---|
| 587 | // De knop staat in de HTML, zodat hij er ook is zonder deze module -- dan doet
|
|---|
| 588 | // hij niets, maar hij springt niet in beeld bij het laden.
|
|---|
| 589 | // De terug-naar-boven-knop staat er tijdelijk uit (Robin, 20-8) -- in
|
|---|
| 590 | // paginamodus doet de bovenbalk dat werk al. De code blijft staan zodat hij
|
|---|
| 591 | // met een regel terug is.
|
|---|
| 592 | knop = null;
|
|---|
| 593 | const oudeKnop = document.getElementById('read-top');
|
|---|
| 594 | if (oudeKnop) oudeKnop.hidden = true;
|
|---|
| 595 |
|
|---|
| 596 | if (opScroll) {
|
|---|
| 597 | window.removeEventListener('scroll', opScroll);
|
|---|
| 598 | window.removeEventListener('scrollend', rustNu);
|
|---|
| 599 | window.removeEventListener('resize', opScroll);
|
|---|
| 600 | window.removeEventListener('wheel', opWiel);
|
|---|
| 601 | window.removeEventListener('touchstart', opRaakStart);
|
|---|
| 602 | window.removeEventListener('touchmove', opRaakBeweeg);
|
|---|
| 603 | window.removeEventListener('keydown', opToets);
|
|---|
| 604 | }
|
|---|
| 605 | // Elke scroll -- van een vinger of van de browser zelf -- houdt het gebaar
|
|---|
| 606 | // levend; pas als het stil blijft mag een nieuwe richting gelden.
|
|---|
| 607 | opScroll = () => {
|
|---|
| 608 | if (knop) toonKnop(knop);
|
|---|
| 609 | planRust();
|
|---|
| 610 | if (document.body.classList.contains('is-paged')) zetBalken();
|
|---|
| 611 | };
|
|---|
| 612 | window.addEventListener('scroll', opScroll, { passive: true });
|
|---|
| 613 | window.addEventListener('scrollend', rustNu);
|
|---|
| 614 | window.addEventListener('resize', opScroll, { passive: true });
|
|---|
| 615 | window.addEventListener('wheel', opWiel, { passive: true });
|
|---|
| 616 | window.addEventListener('touchstart', opRaakStart, { passive: true });
|
|---|
| 617 | window.addEventListener('touchmove', opRaakBeweeg, { passive: true });
|
|---|
| 618 | window.addEventListener('keydown', opToets);
|
|---|
| 619 |
|
|---|
| 620 | const balkBoven = document.getElementById('read-prev');
|
|---|
| 621 | const balkOnder = document.getElementById('read-next-nav');
|
|---|
| 622 | if (balkBoven) balkBoven.onclick = gaOmhoog;
|
|---|
| 623 | if (balkOnder) balkOnder.onclick = () => gaNaar(huidigIndex() + 1);
|
|---|
| 624 |
|
|---|
| 625 | // Alleen in de leesweergave, en alleen op desktop. Bij elke init() opnieuw
|
|---|
| 626 | // beoordelen: van Grid naar Lezen schakelen hoort hem aan te zetten, en
|
|---|
| 627 | // wegnavigeren hoort hem op te ruimen.
|
|---|
| 628 | if (document.body.dataset.feedView === 'reader') startLenis().then(pasPaginaModusToe);
|
|---|
| 629 | else pasPaginaModusToe();
|
|---|
| 630 |
|
|---|
| 631 |
|
|---|
| 632 | }
|
|---|
| 633 |
|
|---|
| 634 | export default { init };
|
|---|