-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscrollIntoView.ts
80 lines (66 loc) · 2.62 KB
/
scrollIntoView.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import findScrollParent from "./findScrollParent";
import isIntoView from "./isIntoView";
export interface WUPScrollOptions {
/** Scroll animation time; Set 0 or null to disable
* @defaultValue 400 */
smoothMs?: number;
/** Offset that must be applied to scrollTop;
* @defaultValue -30 */
offsetTop?: number;
/** Offset that must be applied to scrollLeft;
* @defaultValue -30 */
offsetLeft?: number;
/** Scroll only if element out of view;
* @defaultValue true */
onlyIfNeeded?: boolean;
}
/** Scroll the HTMLElement's parent container such that the element is visible to the user and return promise by animation end */
export default function scrollIntoView(el: HTMLElement, options?: WUPScrollOptions): Promise<void> {
const opts: WUPScrollOptions = { smoothMs: 400, offsetTop: -30, offsetLeft: -30, onlyIfNeeded: true, ...options };
const p = findScrollParent(el);
if (!p) {
return Promise.resolve();
}
const oTop = opts.offsetTop ?? 0;
const oLeft = opts.offsetLeft ?? 0;
const viewResult = isIntoView(el, { scrollParents: [p], offset: [-oTop, -oLeft] });
if (opts.onlyIfNeeded && viewResult.visible) {
return Promise.resolve();
}
const elRect = el.getBoundingClientRect();
const pRect = p.getBoundingClientRect();
const diffTop = elRect.top - pRect.top + oTop;
const diffLeft = elRect.left - pRect.left + oLeft;
if (!opts.smoothMs) {
p.scrollBy({ top: diffTop, left: diffLeft, behavior: "auto" });
return Promise.resolve();
}
const fromTop = p.scrollTop;
const fromLeft = p.scrollLeft;
const needY = viewResult.hiddenY || viewResult.partialHiddenY;
const needX = viewResult.hiddenX || viewResult.partialHiddenX;
const scrollTopEnd = p.scrollHeight - p.offsetHeight;
const toTop = Math.min(scrollTopEnd, Math.max(0, fromTop + diffTop));
const scrollLeftEnd = p.scrollWidth - p.offsetWidth;
const toLeft = Math.min(scrollLeftEnd, Math.max(0, fromLeft + diffLeft));
const animTime = opts.smoothMs;
return new Promise((resolve) => {
let start = 0;
const animate = (t: DOMHighResTimeStamp): void => {
if (!start) {
start = t;
}
const cur = Math.min(t - start, animTime); // to make sure the element stops at exactly pointed value
const v = cur / animTime;
const resTop = fromTop + (toTop - fromTop) * v;
const resLeft = fromLeft + (toLeft - fromLeft) * v;
p.scrollTo({ top: needY ? resTop : undefined, left: needX ? resLeft : undefined });
if (cur === animTime) {
resolve();
return;
}
window.requestAnimationFrame(animate);
};
window.requestAnimationFrame(animate);
});
}