-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.js
135 lines (120 loc) · 5.21 KB
/
main.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
// ### Usability enhancements via JS
/* The intention here is to make the experience more pleasant with JS,
but still have the site usable without it. This is also why we're
attaching all listeners dynamically. */
// --- Clicking on scroll hints skips to the next page (and only indicate this is JS is present)
function scrollToSelector(selector) {
const el = document.querySelector(selector);
el.scrollIntoView({ behavior: 'smooth' });
}
document.querySelector('#js-attach-scroll-wid')
.addEventListener('click', () => scrollToSelector('#what-i-do-wrapper'));
document.querySelector('#js-attach-scroll-whtf')
.addEventListener('click', () => scrollToSelector('#where-to-find-wrapper'));
document.querySelectorAll('.scroll-hint')
.forEach((el) => el.classList.add('click-hint'));
// --- Scrolling parallax is terminated early for smoother experience
const SCROLLING_DOWN = 'SCROLLING_DOWN';
const SCROLLING_DOWN_DONE = 'SCROLLING_DOWN_DONE';
const SCROLLING_UP_PENDING = 'SCROLLING_UP_PENDING';
const SCROLLING_UP = 'SCROLLING_UP';
const IN_COOLDOWN = 'IN_COOLDOWN';
/* This makes the parallax scrolling experience smoother: Browsers induce a noticeable
delay between finishing to scroll the child and switching to scrolling the parent.
We combat this by forcing scroll switching manually. */
class ParallaxHandler {
constructor(el) {
this.el = el;
this.offScreenRefEl = this.el.querySelector('#parallax-offscreen-ref');
this.previousScrollTop = 0;
this.state = SCROLLING_DOWN;
this.scrollUpsInARow = 0;
}
attachListeners() {
// Once widely supported, we could also consider if the
// 'scrollend' event could help us:
// ref: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollend_event
this.el.addEventListener('scroll', () => this.onScroll());
this.el.addEventListener('click', () => scrollToSelector('#parallax-top'))
}
onScroll() {
this.onScrollInternal();
// console.info('scroll state', this.state);
}
onScrollInternal() {
// Important context: 'scroll' events are usually
// delivered best-effort, and we might miss the 'last' scroll.
// If this doesn't work on 'slower' devices, we need to add some
// re-checking using setTimeout / requestAnimationFrame.
if (this.state === IN_COOLDOWN) {
return;
}
this.checkScrollingUp();
if (this.state === SCROLLING_UP_PENDING) {
return;
} else if (this.state === SCROLLING_UP) {
this.state = IN_COOLDOWN;
scrollToSelector('#parallax-top');
window.setTimeout(() => this.state = SCROLLING_DOWN, 200);
return;
}
this.checkScrollingDown();
if (this.state === SCROLLING_DOWN_DONE) {
this.state = IN_COOLDOWN;
this.previousScrollTop = 0;
// Chrome doesn't seem to like scrollIntoView() here, also
// not via various indirections. It works in a click handler
// though. Firefox supports it just fine but OK.
// if (!chrome) scrollToSelector('#what-i-do-wrapper');
const root = document.querySelector(':root');
root.scrollTo({
'top': this.el.offsetHeight,
'behavior': 'smooth',
});
// ^ Also, this doesn't hit the spot exactly in Chrome, it seems
// like the user's scroll events are queded during smooth scrolling,
// which Firefox (imo correctly) ignores them.
window.setTimeout(() => {
this.el.scrollTop = 0; // for if user returns back up
}, 500);
window.setTimeout(() => {
this.state = SCROLLING_DOWN;
}, 2500);
}
}
checkScrollingUp() {
let negativeProgressMade = this.el.scrollTop < this.previousScrollTop;
if (negativeProgressMade) {
this.scrollUpsInARow++;
if (this.scrollUpsInARow < 5) {
this.state = SCROLLING_UP_PENDING;
} else {
this.state = SCROLLING_UP;
}
} else {
this.scrollUpsInARow = 0;
}
this.previousScrollTop = this.el.scrollTop;
}
checkScrollingDown() {
const refBoundingBox = this.offScreenRefEl.getBoundingClientRect();
// This is incorrectly scaled by user zoom in iOS Safari
// Negative values mean 'above screen boundary'
const offScreenAmountPx = refBoundingBox['bottom'];
if (offScreenAmountPx < -30) {
this.state = SCROLLING_DOWN_DONE;
} else { // fallback logic
// ref: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled·
const scrollRoomLeftPx = Math.abs(
this.el.scrollHeight - this.el.clientHeight - this.el.scrollTop
);
if (scrollRoomLeftPx < 1 /* px */) {
this.state = SCROLLING_DOWN_DONE;
}
}
}
}
const handler = new ParallaxHandler(
document.querySelector('#js-attach-parallax-fastlane')
);
window.setTimeout(() => handler.attachListeners());