diff options
| author | Adam Soltys <[email protected]> | 2026-10-09 00:51:19 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-09 00:51:19 -0700 |
| commit | 2d13695021c80892af3604907a8501cc5d42e443 (patch) | |
| tree | 8d0773e0bbf3390fcae6f39878af36ef11e3789c /tools | |
| parent | 806337899029f08bd71fc27be5537e7cc4d590ce (diff) | |
| download | coinosv3-2d13695021c80892af3604907a8501cc5d42e443.tar.gz coinosv3-2d13695021c80892af3604907a8501cc5d42e443.zip | |
Reuse feed overscan to reduce rendering during scroll coasts
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/feed-window-coast-test.js | 63 |
1 files changed, 63 insertions, 0 deletions
diff --git a/tools/feed-window-coast-test.js b/tools/feed-window-coast-test.js new file mode 100644 index 0000000..7c7cead --- /dev/null +++ b/tools/feed-window-coast-test.js @@ -0,0 +1,63 @@ +// A continuous coast reuses the mounted overscan instead of rebuilding it +// one row at a time. Runs the production window/measurement/anchor functions. +import assert from 'node:assert/strict'; +import puppeteer from './puppeteer.js'; +const src = await Bun.file('src/features/messages.js').text(); +const start = src.indexOf(' const WINDOW_AHEAD ='); +const end = src.indexOf(' // While the feed is', start); +assert(start > 0 && end > start); +const script = ` +const h = (tag, attrs = {}, ...kids) => { + const el = document.createElement(tag); + for (const [k,v] of Object.entries(attrs)) el.setAttribute(k,v); + for (const child of kids.flat()) el.append(child); + return el; +}; +const notes = Array.from({ length: 200 }, (_, i) => ({ id: String(i), height: 140 + (i % 4) * 60 })); +const feed = { heights: new Map(notes.map(e => [e.id, e.height])), winList: () => notes }; +let renders = 0, reads = 0; +const rect = Element.prototype.getBoundingClientRect; +Element.prototype.getBoundingClientRect = function () { if (this.matches('.row')) reads++; return rect.call(this); }; +${src.slice(start,end)} +function render() { + renders++; + document.querySelector('.notes-feed').replaceChildren(...windowedRows(feed, notes, + e => h('div', { class: 'row', 'data-key': e.id, style: 'height:'+e.height+'px' }, 'Post '+e.id))); +} +window.addEventListener('scroll', () => windowScrolled(feed)); +render(); +window.runCoast = async () => { + const frame = () => new Promise(requestAnimationFrame); + await frame(); await frame(); renders = reads = 0; + let gaps = 0; + for (const direction of [1, -1]) { + for (let i = 0; i < 180; i++) { + window.scrollBy(0, direction * 50); + await frame(); await frame(); + const rows = [...document.querySelectorAll('.row')]; + if (!rows.some(r => { const b = rect.call(r); return b.top <= 0 && b.bottom >= 0; }) + && !rows.some(r => { const b = rect.call(r); return b.top >= 0 && b.top <= 2; })) gaps++; + } + } + const result = { renders, reads, gaps, y: scrollY, mounted: document.querySelectorAll('.row').length }; + // Large jumps must still replace the window immediately. + scrollTo(0, 30000); await frame(); await frame(); await frame(); + const visible = [...document.querySelectorAll('.row')].some(r => { const b = rect.call(r); return b.bottom > 0 && b.top < innerHeight; }); + return { ...result, jumpCovered: visible }; +};`; +const html = `<style>body{margin:0}.notes-feed{overflow-anchor:none}</style><div class="notes-feed"></div><script>${script}</script>`; +const server = Bun.serve({ port: 0, fetch: () => new Response(html, { headers: { 'Content-Type': 'text/html' } }) }); +const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] }); +try { + const page = await browser.newPage(); + await page.setViewport({ width: 800, height: 800 }); + await page.goto(server.url.href); + const result = await page.evaluate(() => runCoast()); + console.log(result); + assert.equal(result.gaps, 0, 'mounted posts always cover the viewport'); + assert.equal(result.y, 0, 'no accumulated anchor drift'); + assert(result.jumpCovered, 'a scrollbar jump is covered'); + assert(result.mounted < 35, 'DOM remains bounded'); + assert(result.renders <= 20, 'reuse the overscan during a coast'); + assert(result.reads < 2200, 'do not measure every mounted post on every scroll frame'); +} finally { await browser.close(); server.stop(true); } |
