/** * 判断当前侧栏的滚动模型: * - 滚动条覆盖整条(含上下留白)?还是只在内容区? * - 滚到底时,顶部留白是否跟着消失? */ const { spawn } = require('node:child_process') const http = require('node:http') const CHROME = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe' const PORT = 9390 const URL = 'http://localhost:3100/ui/general/button' function get(p) { return new Promise((res, rej) => { http.get({ host: '127.0.0.1', port: PORT, path: p }, (r) => { let d = '' r.on('data', (c) => (d += c)) r.on('end', () => res(JSON.parse(d))) }).on('error', rej) }) } ;(async () => { const chrome = spawn(CHROME, [ '--headless=new', '--disable-gpu', `--remote-debugging-port=${PORT}`, '--user-data-dir=' + require('node:os').tmpdir() + '\\dsh-model', '--no-first-run', '--window-size=1440,900', URL, ], { stdio: 'ignore' }) let target = null for (let i = 0; i < 60; i++) { try { const l = await get('/json/list') target = l.find((t) => t.type === 'page' && t.webSocketDebuggerUrl) if (target) break } catch (e) { /* retry */ } await new Promise((r) => setTimeout(r, 500)) } if (!target) { console.log('NO TARGET'); chrome.kill(); process.exit(1) } const ws = new WebSocket(target.webSocketDebuggerUrl) let id = 0 const pend = new Map() ws.addEventListener('message', (e) => { const m = JSON.parse(e.data) if (m.id && pend.has(m.id)) { pend.get(m.id)(m); pend.delete(m.id) } }) const send = (method, params = {}) => new Promise((res) => { const i = ++id pend.set(i, res) ws.send(JSON.stringify({ id: i, method, params })) }) await new Promise((r) => ws.addEventListener('open', r)) await send('Page.enable'); await send('Runtime.enable') await new Promise((r) => setTimeout(r, 9000)) const snap = `(() => { const sb = document.querySelector('.VPSidebar'); const nav = sb.querySelector('.nav'); const firstLink = sb.querySelector('.g3-nav-link'); const links = sb.querySelectorAll('.g3-nav-link'); const last = links[links.length - 1]; const sr = sb.getBoundingClientRect(); const c = getComputedStyle(sb); const navR = nav.getBoundingClientRect(); return JSON.stringify({ scrollTop: Math.round(sb.scrollTop), sidebarRect: { top: Math.round(sr.top), bottom: Math.round(sr.bottom), h: Math.round(sr.height) }, scrollbarTrack: '容器整高 ' + Math.round(sr.height) + 'px', paddingTop: c.paddingTop, paddingBottom: c.paddingBottom, navTop: Math.round(navR.top), firstLinkTop: Math.round(firstLink.getBoundingClientRect().top), lastLinkBottom: Math.round(last.getBoundingClientRect().bottom), // 关键:内容区相对可视区的可见范围 firstLinkVisible: firstLink.getBoundingClientRect().top >= sr.top, }, null, 2); })()` console.log('=== 初始(scrollTop=0)===') console.log((await send('Runtime.evaluate', { expression: snap, returnByValue: true })).result?.result?.value) await send('Runtime.evaluate', { expression: `const sb=document.querySelector('.VPSidebar'); sb.scrollTop = 200;`, }) await new Promise((r) => setTimeout(r, 700)) console.log('\n=== scrollTop=200(看首行是否上移出顶部留白区)===') console.log((await send('Runtime.evaluate', { expression: snap, returnByValue: true })).result?.result?.value) await send('Runtime.evaluate', { expression: `const sb=document.querySelector('.VPSidebar'); sb.scrollTop = sb.scrollHeight;`, }) await new Promise((r) => setTimeout(r, 700)) console.log('\n=== 滚到底 ===') console.log((await send('Runtime.evaluate', { expression: snap, returnByValue: true })).result?.result?.value) ws.close(); chrome.kill(); process.exit(0) })()