Skip to content

Commit 3e7df02

Browse files
committed
refactor: article auto-scrolling
#26
1 parent 1ba7530 commit 3e7df02

File tree

2 files changed

+56
-30
lines changed

2 files changed

+56
-30
lines changed

src/layouts/WikiArticle.astro

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -129,50 +129,66 @@ export interface Props {
129129

130130
<script>
131131
import lodash from "lodash";
132+
import * as utils from "~/util/DOM";
132133

133-
const headings = document.querySelectorAll(
134-
"article .heading,.article-metadata h1"
135-
);
136-
const outline = document.querySelector("#outline .content");
134+
const outline = utils.assertElement("#outline .content");
135+
const centerColumn = utils.assertElement<HTMLDivElement>(".columns .center");
137136

137+
let init = true;
138138
let topLink: HTMLAnchorElement;
139-
const intersectionObserver = new IntersectionObserver((entries) => {
140-
for (const entry of entries) {
141-
const outlineLink = outline?.querySelector(
142-
`a[href="#${entry.target.id}"]`
143-
);
144-
145-
if (entry.intersectionRatio > 0) {
146-
outlineLink?.classList.add("in-view");
147-
topLink = outlineLink as HTMLAnchorElement;
148-
} else {
149-
outlineLink?.classList.remove("in-view");
139+
const scrollOutline = lodash.debounce(
140+
(smooth = true) => {
141+
if (init) {
142+
smooth = false;
143+
init = false;
144+
145+
// Flush debounced outline scroll functions when scrollend is fired (if browser supports it)
146+
document.addEventListener("scrollend", () => scrollOutline.flush());
147+
centerColumn.addEventListener("scrollend", () => scrollOutline.flush());
150148
}
151-
}
152-
});
153149

154-
headings.forEach((heading) => intersectionObserver.observe(heading));
155-
156-
const scrollOutline = lodash.debounce(
157-
() => {
158-
outline?.scrollTo({
150+
outline.scrollTo({
159151
top: topLink.offsetTop - outline.clientHeight / 2,
160-
behavior: "smooth",
152+
behavior: smooth === false ? "instant" : "smooth",
161153
});
162154
},
163155
50,
164156
{ leading: false, trailing: true }
165157
);
166158

167-
const centerColumn =
168-
document.querySelector<HTMLDivElement>(".columns .center");
159+
const intersectionObserver = new IntersectionObserver((entries) => {
160+
for (const entry of entries) {
161+
const outlineLink = utils.assertElement<HTMLAnchorElement>(
162+
`a[href="#${entry.target.id}"]`,
163+
outline
164+
);
169165

170-
document.addEventListener("scroll", scrollOutline);
171-
centerColumn?.addEventListener("scroll", scrollOutline);
166+
if (entry.intersectionRatio > 0 && outlineLink) {
167+
outlineLink.classList.add("in-view");
168+
topLink = outlineLink;
169+
} else {
170+
outlineLink.classList.remove("in-view");
171+
}
172+
}
173+
});
174+
175+
// Watch headings for intersections
176+
utils
177+
.assertElements("article .heading,.article-metadata h1")
178+
.forEach((heading) => intersectionObserver.observe(heading));
179+
180+
// Watch for article scrolling to scroll outline
181+
document.addEventListener("scroll", () => scrollOutline());
182+
centerColumn.addEventListener("scroll", () => scrollOutline());
172183

173-
// Flush debounced outline scroll functions when scrollend is fired (if browser supports it)
174-
document.addEventListener("scrollend", () => scrollOutline.flush());
175-
centerColumn?.addEventListener("scrollend", () => scrollOutline.flush());
184+
// Force browser to scroll to targeted heading on page load
185+
const target = centerColumn.querySelector<HTMLHeadingElement>(
186+
`article ${location.hash}`
187+
);
188+
if (target) {
189+
console.log("SCROLLING TO TARGET ON PAGE LOAD");
190+
target.scrollIntoView();
191+
}
176192
</script>
177193

178194
<style lang="scss">

src/util/DOM.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,13 @@ export const assertElement = <T extends Element>(
1010

1111
return element;
1212
};
13+
14+
export const assertElements = <T extends Element>(selector: string, parent: Document | Element = document): NodeListOf<T> => {
15+
const elements = parent.querySelectorAll<T>(selector);
16+
17+
if (!elements) {
18+
throw new Error(`Could not find elements using: ${selector}`);
19+
}
20+
21+
return elements
22+
}

0 commit comments

Comments
 (0)