a tool for shared writing and social publishing
1export function scrollIntoViewIfNeeded( 2 el: Element | null, 3 centerIfNeeded: boolean = true, 4 behavior?: ScrollBehavior, 5 rootBottomMargin?: number, 6) { 7 if (!el) { 8 return; 9 } 10 11 let observer = new IntersectionObserver( 12 function ([entry]) { 13 const ratio = entry.intersectionRatio; 14 if (ratio < 1) { 15 let place = 16 ratio <= 0 && centerIfNeeded 17 ? ("center" as const) 18 : ("nearest" as const); 19 el.scrollIntoView({ 20 block: place, 21 inline: place, 22 behavior: behavior ? behavior : "auto", 23 }); 24 25 // If rootBottomMargin is defined, adjust scroll position to keep element away from bottom 26 if (rootBottomMargin) { 27 requestAnimationFrame(() => { 28 let rect = el.getBoundingClientRect(); 29 let scrollContainer = getScrollParent(el); 30 let scrollContainerHeight = scrollContainer.clientHeight; 31 let distanceFromBottom = scrollContainerHeight - rect.bottom; 32 scrollContainer.scrollBy({ 33 top: Math.abs(rootBottomMargin + distanceFromBottom), 34 behavior: behavior ? behavior : "auto", 35 }); 36 }); 37 } 38 } 39 observer.disconnect(); 40 }, 41 { 42 rootMargin: rootBottomMargin 43 ? `0px 0px ${rootBottomMargin}px 0px` 44 : "0px", 45 }, 46 ); 47 observer.observe(el); 48} 49 50function getScrollParent(el: Element) { 51 let parent = el.parentElement; 52 53 while (parent) { 54 const { overflow, overflowY } = window.getComputedStyle(parent); 55 56 if ( 57 overflow === "auto" || 58 overflow === "scroll" || 59 overflowY === "auto" || 60 overflowY === "scroll" 61 ) { 62 return parent; 63 } 64 65 parent = parent.parentElement; 66 } 67 return document.documentElement; 68}