Skip to content

Commit

Permalink
♻️ refactor header component computed scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnsonMao committed Oct 19, 2023
1 parent 5d9ec32 commit 1aed5e6
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 88 deletions.
4 changes: 2 additions & 2 deletions src/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function I18nLayout({
params: { lang },
}: React.PropsWithChildren & RootParams) {
const { common } = await getDictionary(lang);
const logo = {
const avatar = {
src: avatarUrl,
alt: name,
};
Expand All @@ -55,7 +55,7 @@ async function I18nLayout({

return (
<>
<Header logo={logo} menu={menu} />
<Header avatar={avatar} menu={menu} />
{children}
<Footer copyright={copyright} />
</>
Expand Down
10 changes: 6 additions & 4 deletions src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ async function RootPage({ params: { lang } }: RootParams) {

return (
<Container as="main">
<H1 className="pb-6 pl-16 pt-12 text-3xl font-bold dark:text-white">
{metadata.title}
</H1>
<p className="pb-16 text-xl dark:text-white">{metadata.description}</p>
<div className="py-4">
<H1 className="pb-4 pt-16 text-3xl font-bold dark:text-white">
{metadata.title}
</H1>
<p className="pb-16 text-xl dark:text-white">{metadata.description}</p>
</div>
<List Item={Card} items={posts} />
</Container>
);
Expand Down
8 changes: 5 additions & 3 deletions src/app/[lang]/posts/[postId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ async function PostPage({ params: { postId } }: PostParams) {
return (
<>
<Container as="main">
<H1 className="pb-6 pl-16 pt-12 text-3xl font-bold dark:text-white">
{frontmatter.title}
</H1>
<div className="py-4">
<H1 className="pb-4 pt-16 text-3xl font-bold dark:text-white">
{frontmatter.title}
</H1>
</div>
<time className="mt-0">{formattedDate}</time>
<article className="prose prose-xl prose-slate mx-auto dark:prose-invert">
{content}
Expand Down
10 changes: 6 additions & 4 deletions src/app/[lang]/posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ async function RootPage({ params: { lang } }: RootParams) {

return (
<Container as="main">
<H1 className="pb-6 pl-16 pt-12 text-3xl font-bold dark:text-white">
{metadata.title}
</H1>
<p className="pb-16 text-xl dark:text-white">{metadata.description}</p>
<div className="py-4">
<H1 className="pb-4 pt-16 text-3xl font-bold dark:text-white">
{metadata.title}
</H1>
<p className="pb-16 text-xl dark:text-white">{metadata.description}</p>
</div>
<List Item={Card} items={posts} />
</Container>
);
Expand Down
84 changes: 57 additions & 27 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,76 @@
'use client';

import type { StaticImport } from 'next/dist/shared/lib/get-img-props';
import { usePathname } from 'next/navigation';
import { CSSProperties, useEffect, useState, useRef } from 'react';
import { CSSProperties, useEffect, useRef } from 'react';

import useScroll from '@/hooks/useScroll';
import cn from '@/utils/cn';
import getLocale from '@/utils/getLocale';
import { clamp } from '@/utils/math';

import ThemeSwitcher from '../ThemeSwitcher';
import Link from '../Link';
import Logo, { LogoProps } from './Logo';
import Container from '../Container';
import Image from '../Image';
import Link from '../Link';
import ThemeSwitcher from '../ThemeSwitcher';

type MenuItem = {
text: string;
href: LinkWithoutLocalePathProps['href'];
};

type Avatar = {
src: string | StaticImport;
alt: string;
};

export type HeaderProps = {
logo: Omit<LogoProps, 'scrollY' | 'scrollThreshold'>;
avatar: Avatar;
menu: MenuItem[];
scrollThreshold?: number;
};

function Header({ logo, menu }: HeaderProps) {
function Header({ avatar, menu, scrollThreshold = 100 }: HeaderProps) {
const pathname = usePathname();
const [translateY, setTranslateY] = useState(0);
const [scroll] = useScroll();
const headerRef = useRef<HTMLDivElement>(null);
const headerHeight = useRef(0);
const headerTranslateY = useRef(0);
const previousScrollY = useRef(0);
const currentScrollY = Math.floor(scroll.y);
const scrollThreshold = 120;

useEffect(() => {
headerHeight.current = headerRef.current?.clientHeight || 0;
}, []);

useEffect(() => {
const avatarTranslateY = (
clamp(scrollY * -1, scrollThreshold * -1) + scrollThreshold
).toFixed(2);
const avatarScale = (
1.5 -
clamp(scrollY, scrollThreshold) / (scrollThreshold * 2)
).toFixed(2);

const calcHeaderTranslateY = () => {
const scrollY = currentScrollY - scrollThreshold;

if (scrollY <= 0) return setTranslateY(0);
if (scrollY <= 0) return headerTranslateY.current = 0;

const deltaScrollY = previousScrollY.current - scrollY;
const headerHeight = headerRef.current?.clientHeight || 0;
const newHeaderTranslateY = clamp(
headerTranslateY.current + deltaScrollY,
headerHeight * -1
);

previousScrollY.current = scrollY;
headerTranslateY.current = newHeaderTranslateY;

setTranslateY((pre) =>
clamp(pre + deltaScrollY, headerHeight.current * -1)
);
}, [currentScrollY]);
return newHeaderTranslateY;
}

const headerStyles = {
'--header-translate-y': `${calcHeaderTranslateY()}px`,
} as CSSProperties;

const avatarStyles = {
'--avatar-translate-y': `${avatarTranslateY}px`,
'--avatar-scale': avatarScale,
} as CSSProperties;

const isActiveLink = (href: LinkWithoutLocalePathProps['href']) => {
const locale = getLocale(pathname) || '';
Expand All @@ -65,14 +86,23 @@ function Header({ logo, menu }: HeaderProps) {
<Container
as="header"
ref={headerRef}
className="sticky top-0 z-10 flex translate-y-0 items-center justify-between py-7"
style={{ '--tw-translate-y': `${translateY}px` } as CSSProperties}
className="sticky top-0 z-10 flex translate-y-[var(--header-translate-y)] items-center justify-between py-7"
style={headerStyles}
>
<Logo
{...logo}
scrollY={currentScrollY}
scrollThreshold={scrollThreshold}
/>
<Link
href="/"
className="origin-bottom-left translate-y-[var(--avatar-translate-y)] scale-[var(--avatar-scale)]"
style={avatarStyles}
>
<Image
className="rounded-full shadow-black drop-shadow-xl"
width={40}
height={40}
src={avatar.src}
alt={avatar.alt}
priority
/>
</Link>
<nav>
<ul className="flex rounded-full bg-gray-900/60 px-2 backdrop-blur-md">
{menu.map(({ text, href }) => (
Expand Down
48 changes: 0 additions & 48 deletions src/components/Header/Logo.tsx

This file was deleted.

0 comments on commit 1aed5e6

Please sign in to comment.