Created
May 29, 2018 05:16
-
-
Save abalter/d2f30f59ff6eb87f872990cf1e6a1bbc to your computer and use it in GitHub Desktop.
A scrollspy that tries to guess when a section is in focus using the direction of travel. Sort of works.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let focus_height = 160; | |
let click; | |
function spyOnScroll() | |
{ | |
let anchors = $('a[href^="#"][class*="menu-item"]'); | |
let hash_positions = []; | |
let hashes = []; | |
/* | |
Collect arrays of hashes for the menut items | |
and the hash_positions of their associated page | |
locations (as slugs). | |
We use two arrays where a common index coresponds | |
to a common slug. | |
Collecting all this information up front | |
should make the page more responsive. | |
*/ | |
anchors.each(function(index, item) | |
{ | |
hash = item.hash; | |
position = $(hash).position().top; | |
hashes.push(hash); | |
hash_positions.push(position); | |
}); | |
console.log("hashes" + JSON.stringify(hashes)); | |
console.log("positions" + JSON.stringify(hash_positions)); | |
/* | |
The location of the first heading will not be at | |
location 0. For instance, it will be below a nav. | |
So, top_posiion gives the effective zero position | |
for headings. | |
*/ | |
let top_position = Math.min(...hash_positions); | |
console.log("top position=" + top_position); | |
/* | |
Page may get loaded from slug. In which case, the | |
current hash is given by window.location.hash. However | |
if the page loads with no slug, then we assume the | |
initial hash is the top-most. This is the hash that | |
has the smallest corresponding position. | |
*/ | |
let current_hash = window.location.hash; | |
/* | |
This is the slug on page load. Need to make sure | |
we account for when slug is empty. | |
*/ | |
if (current_hash == '') | |
{ | |
current_hash = hashes[0]; | |
} | |
let new_hash = current_hash; | |
console.log("current hash=" + current_hash); | |
let last_window_position = $(window).scrollTop(); | |
/* | |
Get the current position of the window. | |
Because when you reload the screen with a slug | |
for some reason you don't get back to the slug | |
exactly. | |
*/ | |
let last_window_posiiton = $(window).scrollTop(); | |
let current_window_position = last_window_posiiton; | |
let current_hash_offsets = hash_positions.map(x => x - current_window_position - focus_height); | |
let last_hash_offsets = current_hash_offsets; | |
let delta_window_position = 0; | |
let direction = 0; | |
let index = 0; | |
$(window).scroll(function(e) | |
{ | |
console.log("scrolling..."); | |
if (click) | |
{ | |
console.log("scrolled due to click...returning"); | |
e.preventDefault(); | |
click = false; | |
return; | |
} | |
/* | |
Determine if the window scrolled up or down. | |
*/ | |
current_window_position = $(window).scrollTop(); | |
console.log("last position=" + last_window_position, "current position=" + current_window_position); | |
delta_window_position = current_window_position - last_window_position; | |
direction = Math.sign(delta_window_position); | |
console.log("direction=" + direction, "delta_window_position", delta_window_position); | |
last_window_position = current_window_position; | |
current_hash = window.location.hash; | |
/* | |
When creating a scroll spy, you must make a subjective | |
decision about when you consider a section to have the | |
focus and when the focus changes. | |
When a heading is scrolling down from above the page, the | |
That heading's section should lose focus once the heading | |
is a certain distance down from the top of the page, | |
exposing a reasonable amount of the content from the section | |
above. | |
When a heading is scrolling up, that heading's section should | |
get the focus when only a small part of the previous section | |
is still visible. | |
We can accomodate both situations by chosing a distance, say | |
5 lines or 5em*line-height. Or, perhaps a fraction of vh. | |
I've chosen 10rem. | |
*/ | |
/* | |
hash_offsets will tell how far above or below the focus line | |
each heading is. | |
*/ | |
hashes.forEach((o, i) => | |
{ | |
$(o).find('span').html(current_window_position - hash_positions[i]); | |
}); | |
current_hash_offsets = hash_positions.map(x => x - current_window_position - focus_height); | |
let sign_changes = current_hash_offsets.map((o,i) => | |
{ | |
return Math.sign(o) == Math.sign(last_hash_offsets[i]); | |
}); | |
console.log("sign_changes: ", sign_changes); | |
last_hash_offsets = current_hash_offsets; | |
console.log("hash_offsets: " + JSON.stringify(current_hash_offsets)); | |
index = sign_changes.indexOf(false); | |
// index = isCrossedFocusLine(current_hash_offsets, delta_window_position).indexOf(true); | |
console.log("index=" + index); | |
if ( index >= 0) | |
{ | |
new_hash = hashes[index]; | |
console.log("new hash=" + new_hash); | |
$('.nav a[href="' + new_hash + '"]').addClass('active'); // make link active | |
$('.nav a[href!="' + new_hash + '"]').removeClass('active'); //make all others not active | |
window.history.pushState(null, null, new_hash); | |
window.location.hash = new_hash; | |
} | |
}); | |
} | |
$(window).on('load', () => | |
{ | |
$('.menu-item').on('click', () => | |
{ | |
click = true; | |
$('.menu-item').removeClass('active'); | |
$(this).addClass('active'); | |
}); | |
spyOnScroll(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment