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}