Skip to content

Instantly share code, notes, and snippets.

@abalter
Created May 29, 2018 05:16
Show Gist options
  • Save abalter/d2f30f59ff6eb87f872990cf1e6a1bbc to your computer and use it in GitHub Desktop.
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.
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