output-reveal.js (1892B)
1 // Reveal only the start of a foreground result, never its whole (possibly huge) 2 // box. Bounds are visual-viewport coordinates, accounting for the sticky toolbar. 3 export function revealDelta(top, height, toolbarHeight, viewportTop, viewportHeight) { 4 const safeTop = viewportTop + toolbarHeight + 12; 5 const bottom = viewportTop + viewportHeight - 12; 6 if (bottom <= safeTop) 7 return 0; 8 const visibleStart = Math.min(64, height, bottom - safeTop); 9 return top >= safeTop && top + visibleStart <= bottom ? 0 : top - safeTop; 10 } 11 export function outputReveal(toolbar) { 12 let armed = false, revision = 0; 13 const cancel = () => { armed = false; revision++; }; 14 // A user who scrolls elsewhere during a slow run has chosen what to read. 15 window.addEventListener('pagehide', cancel); 16 window.addEventListener('wheel', cancel, { passive: true }); 17 window.addEventListener('touchmove', cancel, { passive: true }); 18 window.addEventListener('keydown', event => { 19 if (['PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown', ' '].includes(event.key)) 20 cancel(); 21 }); 22 function reveal(target) { 23 if (!armed) 24 return; 25 armed = false; 26 const current = revision; 27 requestAnimationFrame(() => { 28 if (current !== revision || !matchMedia('(max-width:740px)').matches || target.closest('[inert]') || !target.getClientRects().length) 29 return; 30 const rect = target.getBoundingClientRect(), viewport = window.visualViewport; 31 const delta = revealDelta(rect.top, rect.height, toolbar.getBoundingClientRect().height, viewport?.offsetTop ?? 0, viewport?.height ?? innerHeight); 32 if (delta) 33 window.scrollBy({ top: delta, behavior: 'instant' }); 34 }); 35 } 36 return { arm() { cancel(); armed = true; }, cancel, reveal }; 37 }