-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhashy.js
174 lines (146 loc) · 4.51 KB
/
hashy.js
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
/**
* Smooth scroll to hash links andoffset by height of fixed navbar.
*/
export default class Hashy {
/**
* @param {string} link_sel [Selector for hash link elements]
* @param {string} offset_sel [Selector for offset elements]
* @param {integer} extra_offset [Value for additional offset]
*/
constructor(link_sel, offset_sel, extra_offset, scroll_time) {
this.scroll_time = scroll_time || 200;
this.offset_elems = document.querySelectorAll(offset_sel);
this.extra_offset = typeof extra_offset === 'number' ? extra_offset : 0;
const links = document.querySelectorAll(link_sel);
for (let i = 0; i < links.length; i++) {
const link = links[i];
link.addEventListener('click', (e) => {
var hash = e.currentTarget.hash;
const elem = this.getElementFromHash(hash);
if (elem) {
e.preventDefault();
this.scrollToHash(hash, false, false, extra_offset);
}
});
}
}
/**
* Find an element by its ID from a hash. Strip the leading # if necessary
*
* @param { String } hash
*/
getElementFromHash(hash) {
if (!hash) {
return null;
}
if (hash.substring(0, 1) === '#') {
hash = hash.substring(1);
}
return document.getElementById(hash);
}
/**
* If there is a hash in the current location, scroll to it using
* offsets as necessary
*
* @param {boolean} quick [If true, moves instantly - no smooth scroll]
* @param {function} callback [Callback function]
* @param {number} extra_offset [Additional offset]
*/
scrollToLocationHash(quick, callback, extra_offset) {
if (window.location.hash) {
this.scrollToHash(window.location.hash, quick, callback, extra_offset);
}
}
/**
* Calculate the size of the offset needed. This will take into account
* elements found using offset_sel as well as extra_offset
*/
offsetHeight() {
let offset_height = 0;
for (let i = 0; i < this.offset_elems.length; i++) {
const elem = this.offset_elems[i];
offset_height += elem.offsetHeight;
}
return offset_height + this.extra_offset;
}
/**
* Scroll the window to a given hash
*
* @param {string} hash [The hash to scroll to, including # symbol]
* @param {boolean} quick [If true, moves instantly - no smooth scroll]
* @param {function} callback [Callback function]
* @param {number} extra_offset [Additional offset]
*/
scrollToHash(hash, quick, callback, extra_offset) {
let offset_height = this.offsetHeight();
if (typeof extra_offset === 'number') {
offset_height += extra_offset;
}
const target = this.getElementFromHash(hash);
if (target) {
let top_dist = this.distFromTop(target);
let scroll_dist = Math.max(0, top_dist - offset_height);
if (quick) {
window.scroll(0, scroll_dist);
this.setHash(hash);
if (callback) {
callback();
}
} else {
this.scrollToY(scroll_dist, this.scroll_time);
}
}
}
/**
* Get the distance between the top of the document and the top of a given
* element
*
* @param {object} elem
*/
distFromTop(elem) {
let distance_scrolled = window.pageYOffset;
let dist_from_viewport_top = elem.getBoundingClientRect().top;
return distance_scrolled + dist_from_viewport_top;
}
/**
* Scroll to a given position
*
* @param {number} y [Distance from top of page to scroll to]
* @param {number} duration [Time to take in ms]
*/
scrollToY(y, duration) {
let start_y = window.pageYOffset;
let diff = y - start_y;
let start;
y = Math.max(Math.min(y, 0));
window.requestAnimationFrame(function step(timestamp) {
if (!start) {
start = timestamp;
}
function inOutQuintEasing(t) {
return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
}
let time = timestamp - start;
let now = (timestamp - start) / duration;
let ease = inOutQuintEasing(now);
let percent = Math.min(time / duration, 1) * ease;
let val = start_y + diff * percent;
window.scrollTo(0, val);
if (time < duration) {
window.requestAnimationFrame(step);
}
});
}
/**
* Set the hash part of the URL without scrolling the window
*
* @param {string} hash [New hash to set, including # symbol]
*/
setHash(hash) {
if (history.pushState) {
history.pushState(null, null, hash);
} else {
location.hash = hash;
}
}
}