Skip to content

Commit acb37fa

Browse files
committed
Make section indicator cursor change with percentage in viewport
1 parent a05d1a9 commit acb37fa

File tree

1 file changed

+86
-23
lines changed

1 file changed

+86
-23
lines changed

assets/js/header_nav.js

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,124 @@
1-
const navBarHeight = document.querySelector('.header').offsetHeight;
2-
document.documentElement.style.setProperty('--scroll-padding', navBarHeight - 1 + "px")
1+
const navBarHeight = document.querySelector(".header").offsetHeight;
2+
document.documentElement.style.setProperty(
3+
"--scroll-padding",
4+
navBarHeight - 1 + "px"
5+
);
36

4-
const headerNav = document.querySelector('.header-nav');
5-
const navToggle = document.querySelector('.mobile-nav-toggle');
7+
const headerNav = document.querySelector(".header-nav");
8+
const navToggle = document.querySelector(".mobile-nav-toggle");
69

710
// Get all sections that have an ID defined
811
const sections = document.querySelectorAll("section[id]");
912
// Add an event listener listening for scroll
1013
window.addEventListener("scroll", () => navHighlighter(navBarHeight));
1114

12-
navToggle.addEventListener('click', navButtonToggler);
13-
14-
15+
navToggle.addEventListener("click", navButtonToggler);
1516

1617
function navButtonToggler() {
17-
const visibility = headerNav.getAttribute('data-visible');
18+
const visibility = headerNav.getAttribute("data-visible");
1819

1920
if (visibility === "false") {
20-
headerNav.setAttribute('data-visible', "true");
21-
navToggle.setAttribute('aria-expanded', "true");
21+
headerNav.setAttribute("data-visible", "true");
22+
navToggle.setAttribute("aria-expanded", "true");
2223
} else {
23-
headerNav.setAttribute('data-visible', "false");
24-
navToggle.setAttribute('aria-expanded', "false");
24+
headerNav.setAttribute("data-visible", "false");
25+
navToggle.setAttribute("aria-expanded", "false");
2526
}
2627
}
2728

28-
const getOffsetTop = element => {
29+
const getOffsetTop = (element) => {
2930
let offsetTop = 0;
3031
while (element) {
3132
offsetTop += element.offsetTop;
3233
element = element.offsetParent;
3334
}
3435
return offsetTop;
36+
};
37+
38+
const bottomVisible = () =>
39+
document.documentElement.clientHeight + window.scrollY >=
40+
(document.documentElement.scrollHeight ||
41+
document.documentElement.clientHeight);
42+
43+
// 'getViewPercentage' was adapted from https://gist.github.com/rijkvanzanten/df73ae28e80b9c6e5030baed4d1a90a6
44+
// Thanks to https://gist.github.com/rijkvanzanten
45+
function getViewPercentage(element) {
46+
const viewport = {
47+
top: window.scrollY,
48+
bottom: window.scrollY + window.innerHeight,
49+
};
50+
51+
const elementBoundingRect = element.getBoundingClientRect();
52+
const elementPos = {
53+
top: elementBoundingRect.y + window.scrollY,
54+
bottom: elementBoundingRect.y + elementBoundingRect.height + window.scrollY,
55+
};
56+
57+
if (viewport.top > elementPos.bottom || viewport.bottom < elementPos.top) {
58+
return 0;
59+
}
60+
61+
// Element is fully within viewport
62+
if (viewport.top < elementPos.top && viewport.bottom > elementPos.bottom) {
63+
return 100;
64+
}
65+
66+
// Element is bigger than the viewport
67+
if (elementPos.top < viewport.top && elementPos.bottom > viewport.bottom) {
68+
return 100;
69+
}
70+
71+
const elementHeight = elementBoundingRect.height;
72+
let elementHeightInView = elementHeight;
73+
74+
if (elementPos.top < viewport.top) {
75+
elementHeightInView = elementHeight - (window.scrollY - elementPos.top);
76+
}
77+
78+
if (elementPos.bottom > viewport.bottom) {
79+
elementHeightInView =
80+
elementHeightInView - (elementPos.bottom - viewport.bottom);
81+
}
82+
83+
const percentageInView = (elementHeightInView / window.innerHeight) * 100;
84+
85+
return Math.round(percentageInView);
3586
}
3687

37-
function navHighlighter(correction = 100) {
88+
function navHighlighter(
89+
correction = 100,
90+
min_percent_accept = 20,
91+
master_percent = 75
92+
) {
3893
// Get current scroll position
3994
let Yscroll = window.scrollY;
4095

4196
// Now we loop through sections to get height, top and ID values for each
42-
sections.forEach(current => {
97+
sections.forEach((current) => {
4398
const sectionHeight = current.offsetHeight;
4499
const sectionTop = getOffsetTop(current) - correction;
45100
let sectionId = current.getAttribute("id");
101+
let viewportPercentage = getViewPercentage(current);
46102

47103
/*
48-
- If our current scroll position enters the space where current section on screen is, add .active class to corresponding navigation link, else remove it
49-
- To know which link needs an active class, we use sectionId variable we are getting while looping through sections as an selector
50-
*/
104+
- If our current scroll position enters the space where current section on screen is
105+
and the section is at least 'min_percent_accept' in view
106+
or the current section is more than 'master_percent' in view, add .active class to corresponding navigation link, else remove it
107+
- To know which link needs an active class, we use sectionId variable we are getting while looping through sections as an selector
108+
*/
51109
if (
52-
Yscroll > sectionTop &&
53-
Yscroll <= sectionTop + sectionHeight
110+
(Yscroll > sectionTop &&
111+
Yscroll <= sectionTop + sectionHeight &&
112+
viewportPercentage > min_percent_accept) ||
113+
viewportPercentage > master_percent
54114
) {
55-
document.querySelector("nav a[href*=" + sectionId + "]").setAttribute("active", "true");
56-
115+
document
116+
.querySelector("nav a[href*=" + sectionId + "]")
117+
.setAttribute("active", "true");
57118
} else {
58-
document.querySelector("nav a[href*=" + sectionId + "]").setAttribute("active", "false");
119+
document
120+
.querySelector("nav a[href*=" + sectionId + "]")
121+
.setAttribute("active", "false");
59122
}
60123
});
61124
}

0 commit comments

Comments
 (0)