forked from Dogfalo/materialize
-
-
Notifications
You must be signed in to change notification settings - Fork 106
/
Copy pathpushpin.ts
165 lines (143 loc) · 4.54 KB
/
pushpin.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
import { Utils } from "./utils";
import { Component, BaseOptions, InitElements, MElement } from "./component";
export interface PushpinOptions extends BaseOptions {
/**
* The distance in pixels from the top of the page where
* the element becomes fixed.
* @default 0
*/
top: number;
/**
* The distance in pixels from the top of the page where
* the elements stops being fixed.
* @default Infinity
*/
bottom: number;
/**
* The offset from the top the element will be fixed at.
* @default 0
*/
offset: number;
/**
* Callback function called when pushpin position changes.
* You are provided with a position string.
* @default null
*/
onPositionChange: (position: "pinned" | "pin-top" | "pin-bottom") => void;
}
let _defaults = {
top: 0,
bottom: Infinity,
offset: 0,
onPositionChange: null
};
export class Pushpin extends Component<PushpinOptions> {
static _pushpins: any[];
originalOffset: any;
constructor(el: HTMLElement, options: Partial<PushpinOptions>) {
super(el, options, Pushpin);
(this.el as any).M_Pushpin = this;
this.options = {
...Pushpin.defaults,
...options
};
this.originalOffset = (this.el as HTMLElement).offsetTop;
Pushpin._pushpins.push(this);
this._setupEventHandlers();
this._updatePosition();
}
static get defaults(): PushpinOptions {
return _defaults;
}
/**
* Initializes instance of Pushpin.
* @param el HTML element.
* @param options Component options.
*/
static init(el: HTMLElement, options?: Partial<PushpinOptions>): Pushpin;
/**
* Initializes instances of Pushpin.
* @param els HTML elements.
* @param options Component options.
*/
static init(els: InitElements<MElement>, options?: Partial<PushpinOptions>): Pushpin[];
/**
* Initializes instances of Pushpin.
* @param els HTML elements.
* @param options Component options.
*/
static init(els: HTMLElement | InitElements<MElement>, options: Partial<PushpinOptions> = {}): Pushpin | Pushpin[] {
return super.init(els, options, Pushpin);
}
static getInstance(el: HTMLElement): Pushpin {
return (el as any).M_Pushpin;
}
destroy() {
(this.el as HTMLElement).style.top = null;
this._removePinClasses();
// Remove pushpin Inst
let index = Pushpin._pushpins.indexOf(this);
Pushpin._pushpins.splice(index, 1);
if (Pushpin._pushpins.length === 0) {
this._removeEventHandlers();
}
(this.el as any).M_Pushpin = undefined;
}
static _updateElements() {
for (let elIndex in Pushpin._pushpins) {
let pInstance = Pushpin._pushpins[elIndex];
pInstance._updatePosition();
}
}
_setupEventHandlers() {
document.addEventListener('scroll', Pushpin._updateElements);
}
_removeEventHandlers() {
document.removeEventListener('scroll', Pushpin._updateElements);
}
_updatePosition() {
let scrolled = Utils.getDocumentScrollTop() + this.options.offset;
if (
this.options.top <= scrolled &&
this.options.bottom >= scrolled &&
!this.el.classList.contains('pinned')
) {
this._removePinClasses();
(this.el as HTMLElement).style.top = `${this.options.offset}px`;
this.el.classList.add('pinned');
// onPositionChange callback
if (typeof this.options.onPositionChange === 'function') {
this.options.onPositionChange.call(this, 'pinned');
}
}
// Add pin-top (when scrolled position is above top)
if (scrolled < this.options.top && !this.el.classList.contains('pin-top')) {
this._removePinClasses();
(this.el as HTMLElement).style.top = '0';
this.el.classList.add('pin-top');
// onPositionChange callback
if (typeof this.options.onPositionChange === 'function') {
this.options.onPositionChange.call(this, 'pin-top');
}
}
// Add pin-bottom (when scrolled position is below bottom)
if (scrolled > this.options.bottom && !this.el.classList.contains('pin-bottom')) {
this._removePinClasses();
this.el.classList.add('pin-bottom');
(this.el as HTMLElement).style.top = `${this.options.bottom - this.originalOffset}px`;
// onPositionChange callback
if (typeof this.options.onPositionChange === 'function') {
this.options.onPositionChange.call(this, 'pin-bottom');
}
}
}
_removePinClasses() {
// IE 11 bug (can't remove multiple classes in one line)
this.el.classList.remove('pin-top');
this.el.classList.remove('pinned');
this.el.classList.remove('pin-bottom');
}
static {
Pushpin._pushpins = [];
}
}