-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgoScroll.js
101 lines (90 loc) · 3.58 KB
/
goScroll.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
const W = window,
D = W.document,
CLASS_NAME = 'auto-scrolling', // className for body element while scrolling
SPEED = 2, // default speed
STOP_EVENT = 'ontouchstart' in W || W.navigator.MaxTouchPoints > 0 || W.navigator.msMaxTouchPoints > 0
? 'touchstart'
: 'wheel';
let scrolling = 0; // flag to stop the scrolling
/**
* Easing function - can be replaced if needed
* @param {number} t - time
* @param {number} b - start value
* @param {number} c - end value
* @param {number} d - duration
* @returns {number}
*/
function easeOutQuad(t, b, c, d) {
return Math.round(-c * (t /= d) * (t - 2) + b);
}
/**
* Find the position of an element inside
* the document or a parent element
* @param {Element} el
* @param {Element|Window} context
* @param {number} scrollTop - scroll position of the parent element
* @returns {number}
*/
function position(el, context, scrollTop) {
if (context.nodeType) {
return el.getBoundingClientRect().top - context.getBoundingClientRect().top + scrollTop;
}
// assuming `context` is the window
return el.getBoundingClientRect().top + D.documentElement.scrollTop;
}
/**
* Scroll stop event handler
* @returns void
*/
function listen() {
scrolling = 0;
}
/**
* Main scroll function, sets the init values from the options object
* and fire the requestAnimationFrame handler function
* @param {object} options
* @param {Element|Window} options.context - Leave blank to scroll the document, pass in an element to scroll its content
* @param {Element|number|void} options.to - Leave blank to scroll to top, or pass in an element or a number
* @param {number} options.offset - Nudge the target position
* @param {number} options.speed - Override the default speed
* @param {function} options.callback - Do something after the scrolling has/was stopped
* @returns void
*/
export default function (options = {}) {
let context = options.context || W, // default context is the window, else a given element
startPos = context[options.context ? 'scrollTop' : 'pageYOffset'],
targetPos = options.to && options.to.nodeType === 1 ? position(options.to, context, startPos) : (options.to || 0),
distance = (targetPos - (options.offset || 0)) - startPos,
startTime = Date.now(),
duration = Math.abs(distance / (options.speed || SPEED)),
targetTime = startTime + duration,
handleScroll = options.context
? y => { context.scrollTop = y }
: y => { context.scrollTo(0, y) };
scrolling = 1; // wave the flag
D.body.classList.add(CLASS_NAME);
context.addEventListener(STOP_EVENT, listen); // let the user stop the animation
W.requestAnimationFrame(scroll); // go!
function scroll() {
let now = Date.now();
// scroll for some time
if (scrolling && now <= targetTime) {
handleScroll(easeOutQuad(now - startTime, startPos, distance, duration));
W.requestAnimationFrame(scroll);
return;
}
// time's up
// if the animation is still allowed,
// be certain we reached our target position.
if (scrolling) {
W.requestAnimationFrame(() => {
handleScroll(targetPos - (options.offset || 0));
});
}
if (typeof options.callback === "function") {
options.callback.call(context, scrolling);
}
context.removeEventListener(STOP_EVENT, listen);
D.body.classList.remove(CLASS_NAME);
}
}