-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathanimated-scroll-to.js
225 lines (192 loc) · 7.87 KB
/
animated-scroll-to.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
(function() {
'use strict';
// desiredOffset - page offset to scroll to
// speed - duration of the scroll per 1000px
function __ANIMATE_SCROLL_TO(desiredOffset) {
var userOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var options = {
speed: 500,
minDuration: 250,
maxDuration: 1500,
cancelOnUserAction: true,
element: window,
horizontal: false,
onComplete: undefined,
passive: true,
offset: 0
};
var optionsKeys = Object.keys(options);
// Override default options
for (var i = 0; i < optionsKeys.length; i++) {
var key = optionsKeys[i];
if (typeof userOptions[key] !== 'undefined') {
options[key] = userOptions[key];
}
}
if (!options.cancelOnUserAction && options.passive) {
options.passive = false;
if (userOptions.passive) {
console && console.warn(
'animated-scroll-to:\n "passive" was set to "false" to prevent errors, ' +
'as using "cancelOnUserAction: false" doesn\'t work with passive events.')
}
}
if (desiredOffset instanceof HTMLElement) {
if (userOptions.element && userOptions.element instanceof HTMLElement) {
if (options.horizontal) {
desiredOffset = (desiredOffset.getBoundingClientRect().left + userOptions.element.scrollLeft)
- userOptions.element.getBoundingClientRect().left;
} else {
desiredOffset = (desiredOffset.getBoundingClientRect().top + userOptions.element.scrollTop)
- userOptions.element.getBoundingClientRect().top;
}
} else if (options.horizontal) {
var scrollLeft = window.scrollX || document.documentElement.scrollLeft;
desiredOffset = scrollLeft + desiredOffset.getBoundingClientRect().left;
} else {
var scrollTop = window.scrollY || document.documentElement.scrollTop;
desiredOffset = scrollTop + desiredOffset.getBoundingClientRect().top;
}
}
// Add additonal user offset
desiredOffset += options.offset
options.isWindow = options.element === window;
var initialScrollPosition = null;
var initialAxisScollPosition = 0;
var maxScroll = null;
if (options.isWindow) {
if (options.horizontal) {
// get cross browser scroll positions
initialScrollPosition = window.scrollX || document.documentElement.scrollLeft;
initialAxisScollPosition = window.scrollY || document.documentElement.scrollTop;
// cross browser document height minus window height
maxScroll = Math.max(
document.body.scrollWidth, document.documentElement.scrollWidth,
document.body.offsetWidth, document.documentElement.offsetWidth,
document.body.clientWidth, document.documentElement.clientWidth
) - window.innerWidth;
} else {
// get cross browser scroll positions
initialScrollPosition = window.scrollY || document.documentElement.scrollTop;
initialAxisScollPosition = window.scrollX || document.documentElement.scrollLeft;
// cross browser document width minus window width
maxScroll = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
) - window.innerHeight;
}
} else {
// DOM element
if (options.horizontal) {
initialScrollPosition = options.element.scrollLeft;
maxScroll = options.element.scrollWidth - options.element.clientWidth;
} else {
initialScrollPosition = options.element.scrollTop;
maxScroll = options.element.scrollHeight - options.element.clientHeight;
}
}
// If the scroll position is greater than maximum available scroll
if (desiredOffset > maxScroll) {
desiredOffset = maxScroll;
}
// Calculate diff to scroll
var diff = desiredOffset - initialScrollPosition;
// Do nothing if the page is already there
if (diff === 0) {
// Execute callback if there is any
if (options.onComplete && typeof options.onComplete === 'function') {
options.onComplete()
}
return;
}
// Calculate duration of the scroll
var duration = Math.abs(Math.round((diff / 1000) * options.speed));
// Set minimum and maximum duration
if (duration < options.minDuration) {
duration = options.minDuration;
} else if (duration > options.maxDuration) {
duration = options.maxDuration;
}
var startingTime = Date.now();
// Request animation frame ID
var requestID = null;
// Method handler
var handleUserEvent = null;
var userEventOptions = { passive: options.passive };
if (options.cancelOnUserAction) {
// Set handler to cancel scroll on user action
handleUserEvent = function() {
removeListeners();
cancelAnimationFrame(requestID);
};
window.addEventListener('keydown', handleUserEvent, userEventOptions);
window.addEventListener('mousedown', handleUserEvent, userEventOptions);
} else {
// Set handler to prevent user actions while scroll is active
handleUserEvent = function(e) { e.preventDefault(); };
window.addEventListener('scroll', handleUserEvent, userEventOptions);
}
window.addEventListener('wheel', handleUserEvent, userEventOptions);
window.addEventListener('touchstart', handleUserEvent, userEventOptions);
var removeListeners = function () {
window.removeEventListener('wheel', handleUserEvent, userEventOptions);
window.removeEventListener('touchstart', handleUserEvent, userEventOptions);
if (options.cancelOnUserAction) {
window.removeEventListener('keydown', handleUserEvent, userEventOptions);
window.removeEventListener('mousedown', handleUserEvent, userEventOptions);
} else {
window.removeEventListener('scroll', handleUserEvent, userEventOptions);
}
};
var step = function () {
var timeDiff = Date.now() - startingTime;
var t = (timeDiff / duration) - 1;
var easing = t * t * t + 1;
var scrollPosition = Math.round(initialScrollPosition + (diff * easing));
var doScroll = function(position) {
if (options.isWindow) {
if (options.horizontal) {
options.element.scrollTo(position, initialAxisScollPosition);
} else {
options.element.scrollTo(initialAxisScollPosition, position);
}
} else if (options.horizontal) {
options.element.scrollLeft = position;
} else {
options.element.scrollTop = position;
}
}
if (timeDiff < duration && scrollPosition !== desiredOffset) {
// If scroll didn't reach desired offset or time is not elapsed
// Scroll to a new position
// And request a new step
doScroll(scrollPosition);
requestID = requestAnimationFrame(step);
} else {
// If the time elapsed or we reached the desired offset
// Set scroll to the desired offset (when rounding made it to be off a pixel or two)
// Clear animation frame to be sure
doScroll(desiredOffset);
cancelAnimationFrame(requestID);
// Remove listeners
removeListeners();
// Animation is complete, execute callback if there is any
if (options.onComplete && typeof options.onComplete === 'function') {
options.onComplete()
}
}
};
// Start animating scroll
requestID = requestAnimationFrame(step);
}
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
module.exports = __ANIMATE_SCROLL_TO;
exports = module.exports;
}
exports.default = __ANIMATE_SCROLL_TO;
} else if (window) {
window.animateScrollTo = __ANIMATE_SCROLL_TO;
}
}).call(this);