Compare commits
No commits in common. "ea4fa0c3421b06c261db6fbf2e20dd549ad45b74" and "14f308f33ebe2c12d71efd168d25f423220f7369" have entirely different histories.
ea4fa0c342
...
14f308f33e
@ -1,80 +0,0 @@
|
|||||||
import type { JSX } from "solid-js";
|
|
||||||
import { createSignal, onCleanup } from "solid-js";
|
|
||||||
|
|
||||||
type SmoothScrollProps = JSX.IntrinsicElements["div"] & {
|
|
||||||
children: JSX.Element;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SmoothScroll(props: SmoothScrollProps) {
|
|
||||||
const [isScrolling, setIsScrolling] = createSignal(false);
|
|
||||||
let animationFrameId: number | null = null;
|
|
||||||
|
|
||||||
const easeOutQuad = (t: number, b: number, c: number, d: number) => {
|
|
||||||
const time = t / d;
|
|
||||||
return -c * time * (time - 2) + b;
|
|
||||||
};
|
|
||||||
|
|
||||||
const smoothScroll = (deltaY: number) => {
|
|
||||||
const scrollSpeed = 3;
|
|
||||||
const currentScroll = window.scrollY;
|
|
||||||
const targetScroll = deltaY * scrollSpeed;
|
|
||||||
const duration = 300;
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
const animateScroll = (currentTime: number) => {
|
|
||||||
const elapsedTime = currentTime - startTime;
|
|
||||||
const ease = easeOutQuad(
|
|
||||||
elapsedTime,
|
|
||||||
currentScroll,
|
|
||||||
targetScroll,
|
|
||||||
duration,
|
|
||||||
);
|
|
||||||
|
|
||||||
window.scrollTo(0, ease);
|
|
||||||
|
|
||||||
if (elapsedTime < duration) {
|
|
||||||
animationFrameId = requestAnimationFrame(animateScroll);
|
|
||||||
} else {
|
|
||||||
setIsScrolling(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
animationFrameId = requestAnimationFrame(animateScroll);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isMobile = () => {
|
|
||||||
const regex =
|
|
||||||
/Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
||||||
return regex.test(navigator.userAgent);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleWheel = (event: WheelEvent) => {
|
|
||||||
if (isMobile()) return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
if (isScrolling()) {
|
|
||||||
if (animationFrameId !== null) {
|
|
||||||
cancelAnimationFrame(animationFrameId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
smoothScroll(event.deltaY);
|
|
||||||
setIsScrolling(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsScrolling(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (animationFrameId !== null) {
|
|
||||||
cancelAnimationFrame(animationFrameId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div {...props} onWheel={handleWheel} style={{ "touch-action": "auto" }}>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -68,7 +68,7 @@ export function Snippet(props: SnippetProps) {
|
|||||||
<div class="pt-4 pl-4">
|
<div class="pt-4 pl-4">
|
||||||
<TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" />
|
<TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" />
|
||||||
|
|
||||||
<div class="mt-4 flex space-x-2 text-xs overflow-x-auto">
|
<div class="mt-4 flex space-x-2 text-xs">
|
||||||
<For each={props.snippets}>
|
<For each={props.snippets}>
|
||||||
{(tab) => (
|
{(tab) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { JSXElement } from "solid-js";
|
import type { JSXElement } from "solid-js";
|
||||||
|
|
||||||
|
// import { TableOfContents } from "@/partials/TableOfContents";
|
||||||
import { PrevNextLinks } from "@/components/PrevNextLinks";
|
import { PrevNextLinks } from "@/components/PrevNextLinks";
|
||||||
import { usePageContext } from "vike-solid/usePageContext";
|
import { usePageContext } from "vike-solid/usePageContext";
|
||||||
import { clientOnly } from "vike-solid/clientOnly";
|
import { clientOnly } from "vike-solid/clientOnly";
|
||||||
@ -21,11 +22,8 @@ export function DocsLayout(props: DocsLayoutProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main
|
<main class="max-w-2xl min-w-0 flex-auto px-4 py-16 lg:max-w-none lg:pr-0 lg:pl-8 xl:px-16 grow">
|
||||||
id="article-content"
|
<article id="article-content">
|
||||||
class="max-w-2xl min-w-0 flex-auto px-4 py-16 lg:max-w-none lg:pr-0 lg:pl-8 xl:px-16 grow"
|
|
||||||
>
|
|
||||||
<article>
|
|
||||||
<DocsHeader
|
<DocsHeader
|
||||||
title={pageContext.exports.frontmatter?.title}
|
title={pageContext.exports.frontmatter?.title}
|
||||||
estimatedReadingTime={pageContext.exports.readingTime?.text}
|
estimatedReadingTime={pageContext.exports.readingTime?.text}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { JSXElement } from "solid-js";
|
import type { JSXElement } from "solid-js";
|
||||||
|
|
||||||
import { usePageContext } from "vike-solid/usePageContext";
|
import { usePageContext } from "vike-solid/usePageContext";
|
||||||
import { SmoothScroll } from "@/components/SmoothScroll";
|
|
||||||
import { HeroSection } from "@/partials/HeroSection";
|
import { HeroSection } from "@/partials/HeroSection";
|
||||||
import { Navigation } from "@/partials/Navigation";
|
import { Navigation } from "@/partials/Navigation";
|
||||||
import { clientOnly } from "vike-solid/clientOnly";
|
import { clientOnly } from "vike-solid/clientOnly";
|
||||||
@ -25,7 +24,7 @@ export default function DefaultLayout(props: DefaultLayoutProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SmoothScroll class="flex w-full flex-col font-sans">
|
<div class="flex w-full flex-col font-sans">
|
||||||
<Header />
|
<Header />
|
||||||
<ReadProgressBar />
|
<ReadProgressBar />
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ export default function DefaultLayout(props: DefaultLayoutProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</SmoothScroll>
|
</div>
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Link } from "@/components/Link";
|
|||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer class="bg-slate-50 text-slate-700">
|
<footer class="bg-slate-50 text-slate-700">
|
||||||
<div class="mx-auto w-full flex flex-col max-w-8xl px-4 sm:px-6 lg:px-8 py-8">
|
<div class="mx-auto w-full flex flex-col max-w-8xl sm:px-6 lg:px-8 py-8">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<Logo class="h-8 w-auto" />
|
<Logo class="h-8 w-auto" />
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
|
|
||||||
import { MobileNavigation } from "@/partials/MobileNavigation";
|
import { MobileNavigation } from "@/partials/MobileNavigation";
|
||||||
|
import { createEffect, createSignal } from "solid-js";
|
||||||
import { Search } from "@/components/Search";
|
import { Search } from "@/components/Search";
|
||||||
import { Link } from "@/components/Link";
|
import { Link } from "@/components/Link";
|
||||||
import { Logo } from "@/components/Logo";
|
import { Logo } from "@/components/Logo";
|
||||||
@ -15,6 +16,19 @@ function GitHubIcon(props: JSX.IntrinsicElements["svg"]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
|
const [isScrolled, setIsScrolled] = createSignal(false);
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
function onScroll() {
|
||||||
|
setIsScrolled(window.scrollY > 0);
|
||||||
|
}
|
||||||
|
onScroll();
|
||||||
|
window.addEventListener("scroll", onScroll, { passive: true });
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("scroll", onScroll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
class={clsx(
|
class={clsx(
|
||||||
@ -35,7 +49,11 @@ export function Header() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="-my-5 mr-6 sm:mr-8 md:mr-0">
|
<div class="-my-5 mr-6 sm:mr-8 md:mr-0">
|
||||||
<Search />
|
<Search
|
||||||
|
// fallback={
|
||||||
|
// <div class="h-6 w-6 animate-pulse rounded-full bg-slate-200" />
|
||||||
|
// }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex basis-0 justify-end gap-6 sm:gap-8 md:grow">
|
<div class="relative flex basis-0 justify-end gap-6 sm:gap-8 md:grow">
|
||||||
|
|||||||
@ -8,42 +8,35 @@ export function ReadProgressBar() {
|
|||||||
const [width, setWidth] = createSignal("0%");
|
const [width, setWidth] = createSignal("0%");
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const articleHeight = articleContentElement.scrollHeight;
|
const scrollTop = window.scrollY;
|
||||||
const topPosition = articleContentElement.getBoundingClientRect().top;
|
const pageHeight = document.documentElement.scrollHeight;
|
||||||
const windowHeight = window.innerHeight;
|
const scrollHeight = articleContentElement.scrollHeight;
|
||||||
|
|
||||||
if (articleHeight <= windowHeight) {
|
const gapWithBottom = pageHeight - scrollHeight;
|
||||||
setWidth("100%");
|
const scrollableHeight = pageHeight - gapWithBottom;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elementScrollTop = -topPosition;
|
const scrollPercentage = Math.round((scrollTop / scrollableHeight) * 100);
|
||||||
const scrollableHeight = articleHeight - windowHeight;
|
|
||||||
const percentage = (elementScrollTop / scrollableHeight) * 100;
|
|
||||||
const clampedPercentage = Math.max(0, Math.min(100, percentage));
|
|
||||||
|
|
||||||
setWidth(`${clampedPercentage}%`);
|
setWidth(`${scrollPercentage}%`);
|
||||||
};
|
};
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
window.addEventListener("scroll", handleScroll);
|
window.addEventListener("scroll", handleScroll);
|
||||||
window.addEventListener("resize", handleScroll);
|
|
||||||
|
|
||||||
handleScroll();
|
handleScroll();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("scroll", handleScroll);
|
window.removeEventListener("scroll", handleScroll);
|
||||||
window.removeEventListener("resize", handleScroll);
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden
|
||||||
class="fixed inset-x-0 bottom-0 lg:top-0 z-50 h-1 w-full bg-violet-50 pointer-events-none"
|
class="sticky inset-x-0 top-20 z-50 h-1 w-full bg-violet-50"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bg-violet-300 h-full transition-all duration-300 ease-out"
|
class="bg-violet-300 h-full transition-all duration-75"
|
||||||
style={{ width: width() }}
|
style={{ width: width() }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export function TableOfContents() {
|
|||||||
if (!section.hash) return null;
|
if (!section.hash) return null;
|
||||||
|
|
||||||
const el = document.getElementById(section.hash);
|
const el = document.getElementById(section.hash);
|
||||||
|
console.log(section.hash, el);
|
||||||
if (!el) return null;
|
if (!el) return null;
|
||||||
|
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user