astro notion blog のサイドバー目次追随
HTML, CSS, JavaScript で記述(参考コードを変更)
Nuovotaka 2023-02-24 4 min readastro notion blog のサイドバー目次追随
ソースコードは HTML, CSS, JavaScript となります。
JavaScript ソースは参考となるコードがありそれを少し手直ししました。
サイドバーの目次を追加
src > components > notion-blocks > TocLink.astro
を作成
---
import * as interfaces from "../../lib/interfaces.ts";
import { buildHeadingId } from "../../lib/blog-helpers.ts";
import { snakeToKebab } from "../../lib/style-helpers.ts";
import "../../styles/notion-color.css";
export interface Props {
blocks: interfaces.Block;
}
const buildHeadingId = (heading) =>
heading.RichTexts.map(
(richText: interfaces.RichText) => richText.Text.Content
)
.join()
.trim();
const { blocks } = Astro.props;
const istoc = blocks.filter(
(b: interfaces.Block) => b.Type === "table_of_contents"
);
const headings = blocks.filter(
(b: interfaces.Block) =>
b.Type === "heading_1" || b.Type === "heading_2" || b.Type === "heading_3"
);
if (istoc.length === 0) {
return;
}
---
<nav class="toc-nav">
{
headings.map((headingBlock: interfaces.Block) => {
const heading =
headingBlock.Heading1 || headingBlock.Heading2 || headingBlock.Heading3;
let indentClass = "";
if (headingBlock.Type === "heading_2") {
indentClass = "indent-1";
} else if (headingBlock.Type === "heading_3") {
indentClass = "indent-2";
}
return (
<span>
<a href={`#${buildHeadingId(heading)}`}>
{heading.RichTexts.map(
(richText: interfaces.RichText) => richText.PlainText
).join("")}
</a>
</span>
);
})
}
</nav>
<style>
.toc-nav {
position: sticky;
top: 2rem;
align-self: start;
}
.toc-nav > span.active > a {
color: #000;
font-weight: bold;
}
.toc-nav > span > a {
display: block;
color: inherit;
line-height: 1.8rem;
font-size: 0.9rem;
text-decoration: underline;
}
.toc-nav > span > a:hover {
background: rgba(241, 241, 239, 1) !important;
}
.toc-nav > span > a.indent-1 {
padding-left: 1.5rem;
}
.toc-nav > span > a.indent-2 {
padding-left: 3rem;
}
</style>
<script is:inline>
//オプション
const options = {
//40% 見えてからコールバック関数を呼び出す
rootMargin: "-40% 0px",
};
// コールバック関数
const callback = (entries) => {
entries.forEach((entry) => {
const id = entry.target.getAttribute("id");
if (entry.isIntersecting) {
document
.querySelector(`nav span a[href="#${id}"]`)
.parentElement.classList.add("active");
} else {
document
.querySelector(`nav span a[href="#${id}"]`)
.parentElement.classList.remove("active");
}
});
};
window.addEventListener("DOMContentLoaded", () => {
const observer = new IntersectionObserver(callback, options);
// 全ての`id`付きの`a`tag要素を抽出
document.querySelectorAll("a[id]").forEach((elem) => {
observer.observe(elem);
});
});
</script>
querySelectorAll(”a[id]”)
a tag に id が付与されていてサイドバーでクラスに’.active’を追加したいときに id が必要になります。
上記の画像では h6 に id が無く’null’ となってしまうため、a[id] で抽出することとしました。
TocLink を slug へ追加
src > pages > blog > [slug].astro
ファイルを変更
TocLink
を追加
-
import 文を Frontmatter の部分へ追加
import TocLink from '../../components/notion-blocks/TocLink.astro'
-
サイドバーの固定目次の追加
aside の閉じタグの直前に追加
<TocLink blocks={blocks} />
[slug].astro の一部抜粋コードが下記です。
---
...
...
...
import TocLink from "../../components/notion-blocks/TocLink.astro";
....
----
<Layout
title={post.Title}
description={post.Excerpt}
path={getPostLink(post.Slug)}
>
<div className={styles.container}>
<main>
<div className={styles.post}>
<PostDate post={post} />
<PostTags post={post} />
<PostTitle post={post} enableLink={false} />
<PostBody blocks={blocks} />
<footer></footer>
</div>
</main>
<aside>
<BlogPostsLink
heading="Posts in the same category"
posts={postsHavingSameTag.filter(
(p: interfaces.Post) => p.Slug !== post.Slug
)}
/>
<BlogPostsLink heading="Recommended" posts={rankedPosts} />
<BlogPostsLink heading="Latest posts" posts={recentPosts} />
<BlogTagsLink heading="Categories" tags={tags} />
<TocLink blocks={blocks} />
</aside>
</div>
</Layout>
参考
-
IntersectionObserver について
-
querySelector, querySelectorAll について
-
参考コード Smooth Scrolling Sticky ScrollSpy Navigation スクロールに応じて現在地表示する固定目次を JavaScript で作る(jQuery なし)| BringFlower