This repository has been archived by the owner on Jun 14, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
scroll.service.ts
179 lines (146 loc) · 4.57 KB
/
scroll.service.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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import { Ruler } from '../ruler/ruler-service';
import { Screen } from '../screen/screen-service';
import { AppAutoscrollAnchor } from './auto-scroll/anchor';
import { ScrollWatcher } from './watcher.service';
// Polyfill smooth scrolling.
if (!GJ_IS_SSR) {
require('smoothscroll-polyfill').polyfill();
}
export type ScrollContext = HTMLElement | HTMLDocument;
export class Scroll {
static shouldAutoScroll = true;
static autoscrollAnchor?: AppAutoscrollAnchor;
// For SSR context we have to set this to undefined. No methods should be
// called that would use the context.
static watcher: ScrollWatcher;
static offsetTop = 0;
/**
* Sets the extra offset for scrolling. This can be used if there is a fixed
* nav on the top that we need to always offset from.
*/
static setOffsetTop(offset: number) {
this.offsetTop = offset;
}
/**
* Sets the element that we will scroll when any scroll commands are issued.
*/
static init() {
this.watcher = new ScrollWatcher(document);
// Set up events to let the Screen service know if we're scrolling or not.
this.watcher.start.subscribe(() => (Screen.isScrolling = true));
this.watcher.stop.subscribe(() => (Screen.isScrolling = false));
}
static getScrollTop(element?: ScrollContext): number {
if (!element) {
element = document;
}
if (element instanceof HTMLDocument) {
return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
}
return element.scrollTop;
}
static getScrollLeft(element?: ScrollContext): number {
if (!element) {
element = document;
}
if (element instanceof HTMLDocument) {
return (
window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft
);
}
return element.scrollLeft;
}
static getScrollHeight(element?: ScrollContext): number {
if (!element) {
element = document;
}
if (element instanceof HTMLDocument) {
return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
}
return element.scrollHeight;
}
static getScrollWidth(element?: ScrollContext): number {
if (!element) {
element = document;
}
if (element instanceof HTMLDocument) {
return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
}
return element.scrollWidth;
}
static getScrollWindowHeight(element?: ScrollContext): number {
if (!element) {
element = document;
}
return element === document ? window.innerHeight : (element as HTMLElement).clientHeight;
}
static getScrollWindowWidth(element?: ScrollContext): number {
if (!element) {
element = document;
}
return element === document ? window.innerWidth : (element as HTMLElement).clientWidth;
}
/**
* Returns the element's offset from the top of the scroll context.
*/
static getElementOffsetTopFromContext(element: HTMLElement) {
return Ruler.offset(element).top - this.offsetTop;
}
/**
* Returns the element's offset from the bottom of the scroll context.
*/
static getElementOffsetBottomFromContext(element: HTMLElement) {
const { top, height } = Ruler.offset(element);
return top + height;
}
/**
* Scrolls to the element passed in.
*/
static to(input: string | number | HTMLElement, options: { animate?: boolean } = {}) {
if (GJ_IS_SSR) {
return;
}
let to = 0;
let element: HTMLElement | null = null;
if (options.animate === undefined) {
options.animate = true;
}
if (typeof input === 'number') {
to = input;
} else if (typeof input === 'string') {
element = document.getElementById(input);
if (!element) {
throw new Error(`Couldn't find element: ${input}`);
}
} else {
element = input;
}
// Just make sure that all dom compilation is over.
setTimeout(() => {
if (element) {
// We don't scroll the full way to down to the element. Do it
// based on the screen's height, so that mobile and stuff works
// well too. This is because I think it's kind of annoying when
// the edge hits the exact top of the browser.
this.scrollToElement(element, Screen.height * 0.1 + this.offsetTop, options);
} else {
this.scrollTo(to, options);
}
}, 20);
}
private static scrollToElement(
element: HTMLElement,
offset: number,
options: { animate?: boolean } = {}
) {
let top = this.getScrollTop(document) + element.getBoundingClientRect().top - offset;
this.scrollTo(top, options);
}
private static scrollTo(to: number, options: { animate?: boolean } = {}) {
window.scrollTo({ top: to, behavior: options.animate ? 'smooth' : 'auto' });
}
}
if (!GJ_IS_SSR) {
// Sets the document as the scroll context.
Scroll.init();
}