-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwheelscrolling.js
More file actions
110 lines (92 loc) · 3.36 KB
/
Copy pathwheelscrolling.js
File metadata and controls
110 lines (92 loc) · 3.36 KB
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
// ==================== Wheel-scrolling plugin ====================
const wheelScrollingEventClass = ".wheel-scrolling";
// Defines default options for the wheel-scrolling plugin.
let wheelScrollingDefaults = {
// The scroll offset of a single wheel delta event.
// Values between 0 (exclusive) and 1 (inclusive) are treated as the ratio of the element's
// visible height. Values greater than 1 are absolute pixel offsets.
step: 0.1,
// The duration of a single scroll event animation, in milliseconds.
duration: 150,
// Specifies whether wheel scrolling is disabled completely.
disabled: false
}
// Enables custom mouse wheel scrolling on each selected element.
function wheelScrolling(options) {
return this.forEach(element => {
if (element.F.hasEventListeners("wheel" + wheelScrollingEventClass)) return; // Already done
let opt = F.initOptions("wheelScrolling", element, {}, options);
let step = opt.step;
if (typeof step !== "number" || isNaN(step) || step <= 0)
step = wheelScrollingDefaults.step;
let duration = opt.duration;
if (typeof duration !== "number" || isNaN(duration) || duration < 0)
duration = wheelScrollingDefaults.duration;
let scrollTarget, scrollTargetTimeout, scrollAnimation;
element.F.on("wheel" + wheelScrollingEventClass, event => {
event.preventDefault();
if (opt.disabled) return;
if (scrollTarget === undefined)
scrollTarget = element.scrollTop;
let newScroll = scrollTarget;
let offset;
if (step <= 1)
offset = Math.sign(event.deltaY) * step * element.clientHeight;
else
offset = Math.sign(event.deltaY) * step;
newScroll += offset;
if (newScroll < 0)
newScroll = 0;
if (newScroll > element.scrollHeight - element.clientHeight)
newScroll = element.scrollHeight - element.clientHeight;
if (newScroll != scrollTarget) {
scrollTarget = newScroll;
scrollAnimation && scrollAnimation.cancel();
scrollAnimation = animate(element.scrollTop, scrollTarget, v => element.scrollTop = v, duration);
}
clearTimeout(scrollTargetTimeout);
scrollTargetTimeout = setTimeout(() => scrollTarget = undefined, duration);
});
});
}
// Deinitializes the plugin.
function deinit() {
return this.forEach(elem => {
if (!elem.F.hasEventListeners("wheel" + wheelScrollingEventClass)) return;
elem.off(wheelScrollingEventClass);
F.deleteOptions("wheelScrolling", elem);
});
}
F.registerPlugin("wheelScrolling", wheelScrolling, {
defaultOptions: wheelScrollingDefaults,
methods: {
deinit: deinit
}
});
// ==================== Private functions ====================
function animate(startValue, endValue, setter, duration) {
if (typeof duration !== "number" || isNaN(duration))
duration = 200;
if (document.hidden) {
setter(endValue);
return;
}
const animationDelay = 10; // 100 fps
const animationCount = Math.round(duration / animationDelay);
let animationPosition = 0;
let timeout;
animationStep();
return {
// Cancels the running animation.
cancel: () => {
clearTimeout(timeout);
}
};
function animationStep() {
if (++animationPosition <= animationCount) {
const r = Math.sin(animationPosition / animationCount * Math.PI / 2);
setter(startValue + (endValue - startValue) * r);
timeout = setTimeout(animationStep, animationDelay);
}
}
}