mirror of
https://github.com/CaiJimmy/hugo-theme-stack.git
synced 2025-04-29 20:13:31 +08:00
Add first try at scrollspy (broken right now)
This commit is contained in:
parent
86cbc1b682
commit
6d8d55b6d0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
public
|
||||
resources
|
||||
assets/jsconfig.json
|
||||
.hugo_build.lock
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
}
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
overflow-y: scroll;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
* {
|
||||
|
@ -122,7 +122,6 @@
|
||||
}
|
||||
|
||||
.article-page.has-toc {
|
||||
scroll-behavior: smooth;
|
||||
|
||||
.left-sidebar {
|
||||
display: none;
|
||||
@ -193,6 +192,10 @@
|
||||
color: var(--card-text-color-main);
|
||||
overflow: hidden;
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--card-separator-color);
|
||||
}
|
||||
|
||||
#TableOfContents {
|
||||
overflow-x: auto;
|
||||
max-height: 75vh;
|
||||
@ -220,7 +223,7 @@
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 15px 20px;
|
||||
margin: 15px 0 15px 20px;
|
||||
padding: 5px;
|
||||
|
||||
& > ol,
|
||||
@ -234,6 +237,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
li.active-class > a {
|
||||
border-left: var(--heading-border-size) solid var(--accent-color);
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
& > ul > li.active-class > a {
|
||||
margin-left: calc(-25px - 1em);
|
||||
padding-left: calc(25px + 1em - var(--heading-border-size));
|
||||
}
|
||||
|
||||
& > ul > li > ul > li.active-class > a {
|
||||
margin-left: calc(-60px - 1em);
|
||||
padding-left: calc(60px + 1em - var(--heading-border-size));
|
||||
}
|
||||
|
||||
& > ul > li > ul > li > ul > li.active-class > a {
|
||||
margin-left: calc(-95px - 1em);
|
||||
padding-left: calc(95px + 1em - var(--heading-border-size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { getColor } from 'ts/color';
|
||||
import menu from 'ts/menu';
|
||||
import createElement from 'ts/createElement';
|
||||
import StackColorScheme from 'ts/colorScheme';
|
||||
import { setupScrollspy } from 'ts/scrollspy';
|
||||
|
||||
let Stack = {
|
||||
init: () => {
|
||||
@ -21,6 +22,7 @@ let Stack = {
|
||||
const articleContent = document.querySelector('.article-content') as HTMLElement;
|
||||
if (articleContent) {
|
||||
new StackGallery(articleContent);
|
||||
setupScrollspy(".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]", "#TableOfContents", "#TableOfContents li", "active-class")
|
||||
}
|
||||
|
||||
/**
|
||||
|
99
assets/ts/scrollspy.ts
Normal file
99
assets/ts/scrollspy.ts
Normal file
@ -0,0 +1,99 @@
|
||||
// While solutions for debouncing like the ones in https://gomakethings.com/debouncing-your-javascript-events/ could work,
|
||||
// we do need an actual debouncing of scroll events in order to only capture the "end" of the scroll.
|
||||
function debounced(func: Function) {
|
||||
let timeout;
|
||||
return (e: Event) => {
|
||||
/*
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = window.requestAnimationFrame(func);
|
||||
*/
|
||||
if (timeout) {
|
||||
window.cancelAnimationFrame(timeout);
|
||||
}
|
||||
|
||||
timeout = window.requestAnimationFrame(() => func(e));
|
||||
}
|
||||
}
|
||||
|
||||
function setupScrollspy(headersQuery: string, tocQuery: string, navigationQuery: string, activeClass: string) {
|
||||
let headers = document.querySelectorAll(headersQuery);
|
||||
if (!headers) {
|
||||
console.warn("No header matched query", headers);
|
||||
return;
|
||||
}
|
||||
|
||||
let scrollableNavigation = document.querySelector(tocQuery);
|
||||
if (!scrollableNavigation) {
|
||||
console.warn("No toc matched query", tocQuery);
|
||||
return;
|
||||
}
|
||||
|
||||
let navigation = document.querySelectorAll(navigationQuery);
|
||||
if (!navigation) {
|
||||
console.warn("No navigation matched query", navigationQuery);
|
||||
return;
|
||||
}
|
||||
|
||||
let sectionsOffsets = [];
|
||||
|
||||
headers.forEach((header: HTMLElement) => { sectionsOffsets.push({ id: header.id, offset: header.offsetTop }) });
|
||||
sectionsOffsets.sort((a, b) => a.offset - b.offset);
|
||||
|
||||
let activeSectionLink: Element;
|
||||
let tocHovered: boolean = false;
|
||||
|
||||
function hoverHandler(isHovered: boolean) {
|
||||
tocHovered = isHovered;
|
||||
}
|
||||
|
||||
function scrollHandler(e: Event) {
|
||||
let scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
|
||||
let newActiveSection: HTMLElement;
|
||||
|
||||
sectionsOffsets.forEach((section) => {
|
||||
if (scrollPosition >= section.offset - 20) {
|
||||
newActiveSection = document.getElementById(section.id);
|
||||
}
|
||||
});
|
||||
|
||||
let newActiveSectionLink: HTMLElement;
|
||||
if (newActiveSection) {
|
||||
for (let i = 0; i < navigation.length; i++) {
|
||||
let link = navigation[i].querySelector("a");
|
||||
if (link.getAttribute("href") === "#" + newActiveSection.id) {
|
||||
newActiveSectionLink = navigation[i] as HTMLElement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newActiveSection && !newActiveSectionLink) {
|
||||
console.warn("No link found for section", newActiveSection);
|
||||
} else if (newActiveSectionLink !== activeSectionLink) {
|
||||
if (activeSectionLink)
|
||||
activeSectionLink.classList.remove(activeClass);
|
||||
if (newActiveSectionLink) {
|
||||
newActiveSectionLink.classList.add(activeClass);
|
||||
if (!tocHovered) {
|
||||
// Scroll so that newActiveSectionLink is in the middle of scrollableNavigation, except when it's from a manual click (hence the tocHovered check)
|
||||
let textHeight = newActiveSectionLink.querySelector("a").offsetHeight;
|
||||
let scrollTop = newActiveSectionLink.offsetTop - scrollableNavigation.offsetHeight / 2 + textHeight / 2 - scrollableNavigation.offsetTop;
|
||||
if (scrollTop < 0) {
|
||||
scrollTop = 0;
|
||||
}
|
||||
scrollableNavigation.scrollTo({ top: scrollTop, behavior: "auto" });
|
||||
}
|
||||
}
|
||||
activeSectionLink = newActiveSectionLink;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", debounced(scrollHandler));
|
||||
scrollableNavigation.addEventListener("mouseenter", debounced(() => hoverHandler(true)));
|
||||
scrollableNavigation.addEventListener("mouseleave", debounced(() => hoverHandler(false)));
|
||||
}
|
||||
|
||||
export { setupScrollspy };
|
Loading…
Reference in New Issue
Block a user