Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/components/BackToTopButton.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
import IconChevronLeft from "@/assets/icons/IconChevronLeft.svg";

/*
In #btt-btn-container, left position is `left-[calc((var(--container-3xl)/2)+50%+1rem)]`.
This is because we use `max-w-3xl` in `PostDetails.astro`.
If `max-width` in `PostDetails.astro` get updated, make sure to update the `var(--container-3xl)` as well.
max-w-3xl => var(--container-3xl)
max-w-4xl => var(--container-4xl)
max-w-5xl => var(--container-5xl)
etc...
*/
---

<div
id="btt-btn-container"
class:list={[
"fixed right-4 bottom-8 xl:right-auto xl:left-[calc((var(--container-3xl)/2)+50%+1rem)]",
"translate-y-14 opacity-0 transition-all duration-500",
]}
>
<button
data-button="back-to-top"
class:list={[
"group relative",
"size-14 rounded-full px-2 py-1 shadow-xl lg:h-8 lg:w-fit lg:rounded-md lg:shadow-none",
"bg-background lg:bg-transparent",
]}
>
<span
id="progress-indicator"
class="absolute inset-0 -z-10 block size-14 scale-110 rounded-full bg-transparent lg:hidden lg:h-8 lg:rounded-md"
></span>
<IconChevronLeft class="inline-block rotate-90 lg:hidden" />
<span
class="sr-only underline decoration-dashed decoration-2 underline-offset-4 group-hover:text-accent lg:not-sr-only"
>
<span class="text-xl" tabindex="0">&uarr;</span> Back To Top
</span>
</button>
</div>

<script is:inline data-astro-rerun>
/** Scrolls the document to the top when
* the "Back to Top" button is clicked. */
function backToTop() {
const rootElement = document.documentElement;
const btnContainer = document.querySelector("#btt-btn-container");
const backToTopBtn = document.querySelector("[data-button='back-to-top']");
const progressIndicator = document.querySelector("#progress-indicator");

if (!rootElement || !btnContainer || !backToTopBtn || !progressIndicator)
return;

// Attach click event handler for back-to-top button
backToTopBtn.addEventListener("click", () => {
document.body.scrollTop = 0; // For Safari
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
});

// Handle button visibility according to scroll position
let lastVisible = null;
function handleScroll() {
const scrollTotal = rootElement.scrollHeight - rootElement.clientHeight;
const scrollTop = rootElement.scrollTop;
const scrollPercent = Math.floor((scrollTop / scrollTotal) * 100);

progressIndicator.style.setProperty(
"background-image",
`conic-gradient(var(--accent), var(--accent) ${scrollPercent}%, transparent ${scrollPercent}%)`
);

const isVisible = scrollTop / scrollTotal > 0.3;

if (isVisible !== lastVisible) {
btnContainer.classList.toggle("opacity-100", isVisible);
btnContainer.classList.toggle("translate-y-0", isVisible);
btnContainer.classList.toggle("opacity-0", !isVisible);
btnContainer.classList.toggle("translate-y-14", !isVisible);
lastVisible = isVisible;
}
}

let ticking = false;
document.addEventListener("scroll", () => {
if (!ticking) {
window.requestAnimationFrame(() => {
handleScroll();
ticking = false;
});
ticking = true;
}
});
}
backToTop();
</script>
29 changes: 7 additions & 22 deletions src/layouts/PostDetails.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Datetime from "@/components/Datetime.astro";
import EditPost from "@/components/EditPost.astro";
import ShareLinks from "@/components/ShareLinks.astro";
import BackButton from "@/components/BackButton.astro";
import BackToTopButton from "@/components/BackToTopButton.astro";
import { getPath } from "@/utils/getPath";
import { slugifyStr } from "@/utils/slugify";
import IconChevronLeft from "@/assets/icons/IconChevronLeft.svg";
Expand Down Expand Up @@ -109,22 +110,16 @@ const nextPost =

<EditPost class="sm:hidden" {hideEditPost} {post} />

<ul class="mt-4 mb-8 sm:my-8">
{tags.map(tag => <Tag tag={slugifyStr(tag)} tagName={tag} />)}
</ul>
<BackToTopButton />

<div
class="flex flex-col items-center justify-between gap-6 sm:flex-row sm:items-end sm:gap-4"
class="flex flex-col items-center justify-between gap-6 sm:flex-row sm:items-start sm:gap-8"
>
<ShareLinks />
<ul class="self-start">
{tags.map(tag => <Tag tag={slugifyStr(tag)} tagName={tag} />)}
</ul>

<button
id="back-to-top"
class="focus-outline py-1 whitespace-nowrap hover:opacity-75"
>
<IconChevronLeft class="inline-block rotate-90" />
<span>Back to Top</span>
</button>
<ShareLinks />
</div>

<hr class="my-6 border-dashed" />
Expand Down Expand Up @@ -270,16 +265,6 @@ const nextPost =
}
attachCopyButtons();

/** Scrolls the document to the top when
* the "Back to Top" button is clicked. */
function backToTop() {
document.querySelector("#back-to-top")?.addEventListener("click", () => {
document.body.scrollTop = 0; // For Safari
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
});
}
backToTop();

/* Go to page start after page swap */
document.addEventListener("astro:after-swap", () =>
window.scrollTo({ left: 0, top: 0, behavior: "instant" })
Expand Down
Loading