-
Notifications
You must be signed in to change notification settings - Fork 1
/
directive-watch-scroll-over.ts
129 lines (118 loc) · 3.9 KB
/
directive-watch-scroll-over.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
import {DomWatcher} from '../dom/dom-watcher';
import * as mathf from '../mathf/mathf';
import {INgDisposable} from './i-ng-disposable';
class WatchScrollOverController implements INgDisposable {
private el: HTMLElement;
private $scope: ng.IScope;
private $attrs: ng.IAttributes;
private cssClass: string;
private cssOutClass: string;
private targetElements: Array<HTMLElement> = [];
private watcher: DomWatcher;
static get $inject() {
return ['$scope', '$element', '$attrs'];
}
constructor(
$scope: ng.IScope,
$element: ng.IAugmentedJQuery,
$attrs: ng.IAttributes
) {
this.$scope = $scope;
this.el = $element[0];
this.$attrs = $attrs;
this.cssClass = this.$attrs.watchScrollOverClass;
this.cssOutClass = this.$attrs.watchScrollOverOutClass;
this.watcher = new DomWatcher();
let query = this.$attrs.watchScrollOverQuery;
if ($attrs.watchScrollOverQueryEval) {
query = $scope.$eval(query)[0][0];
}
window.setTimeout(() => {
this.targetElements = Array.from(document.querySelectorAll(query));
this.watcher.add({
element: window,
on: 'scroll',
callback: this.scroll.bind(this),
eventOptions: {passive: true},
});
this.scroll();
});
$scope.$on('$destroy', () => {
this.dispose();
});
}
private scroll(): void {
const top = this.el.getBoundingClientRect().top + window.scrollY;
let shouldAdd = false;
this.targetElements.forEach(targetElement => {
const targetBox = targetElement.getBoundingClientRect();
const targetTop = targetBox.top + window.scrollY;
const targetBottom = targetTop + targetBox.height;
if (mathf.isBetween(top, targetTop, targetBottom, true)) {
shouldAdd = true;
}
});
if (shouldAdd) {
this.el.classList.add(this.cssClass);
this.cssOutClass && this.el.classList.remove(this.cssOutClass);
} else {
this.cssOutClass && this.el.classList.add(this.cssOutClass);
this.el.classList.remove(this.cssClass);
}
}
public dispose(): void {
this.watcher.dispose();
}
}
/**
* On a large single page, let's say you have a fix header that is a bunch
* of jump links.
*
* Options:
* - watch-scroll-over-query (string): query to find the target element. If the query
* returns multiple elements the first element is selected. Example: #id, .class
* - watch-scroll-over-query-eval (bool): Whether to run an eval on the query.
* - watch-scroll-over-class (string): The class name to append when the current
* directive element resides over the target element.
* - watch-scroll-over-out-class (string): The class name to append when the current
* directive element goes out.
*
* ```
* <div class="header" style="position: fixed; top: 0">
*
* <div class="nav"
* watch-scroll-over
* watch-scroll-over-query=".section-1"
* watch-scroll-over-class="active"
* watch-scroll-over-class="out"
* > Section 1</div>
* <div class="nav"
* watch-scroll-over
* watch-scroll-over-query=".section-2"
* watch-scroll-over-class="active"> Section 2</div>
* <div class="nav"
* watch-scroll-over
watch-scroll-over-query="[['.' + section.id]]"
watch-scroll-over-query-eval="true"
* watch-scroll-over-class="active"> Section 3</div>
* </div>
*
* <div class="content">
* <div class=".section-1">...</div>
* <div class=".section-2">...</div>
* <div class=".section-3">...</div>
* </div>
* ```
*
* Now as the user scrolls, when the header nav goes over the .section-1 div,
* the first nav item will receive the class "active" which gets removed
* when it goes out the section.
*
* TODO (uxder): More optimization around this.
*/
export const watchScrollOverDirective = () => {
return {
restrict: 'A',
controller: WatchScrollOverController,
};
};