Built Portfolio Page - 6 (Navigation bar effect)

Steps:

  1. Modify Header.tsx
  2. Add motion effect
  3. Install react-intersection-observer
  4. Add ActiveSectionContext to pass context
  5. Add ActiveSectionContextProvider to Layout
  6. Consume context
  7. Add more control to make movement smoothly

Modify Header.tsx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<Link
className={`flex w-full items-center justify-center px-3 py-3 hover:text-gray-950
transition ${activeSection === link.name? "text-gray-950":""}`}
href={link.hash}
onClick={()=> setActiveSection(link.name)}
>
{link.name}

{link.name === activeSection && (
<span className='bg-gray-100 rounded-full absolute inset-0 -z-10'></span>
)}
</Link>
....

Add motion effect

1
2
3
4
5
6
7
8
9
10
11
12
13
...
{link.name === activeSection && (
<motion.span
className='bg-gray-100 rounded-full absolute inset-0 -z-10'
layoutId='activeSection'
transition={{
type: "spring",
stiffness: 380,
damping: 30,
}}
>
</motion.span>
...

transition={{ type: "spring", stiffness: 380, damping: 30, }}: 这是 Framer Motion 的 transition 属性,用于定义动画的过渡效果:

  • type: "spring": 过渡类型设置为弹簧动画。
  • stiffness: 380: 设置弹簧的刚度,值越大,动画越快。
  • damping: 30: 设置弹簧的阻尼,值越大,动画越缓和。

Install react-intersection-observer

run:

1
npm i react-intersection-observer

Add ActiveSectionContext to pass context

Add src/context/ActiveSectionContext.tsx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"use client"

import React, { useState,createContext, useContext } from 'react';
import { links } from '@/libs/data';

type SectionName = typeof links[number]["name"];

type ActiveSectionContextProviderProps ={
children: React.ReactNode;
}

type ActiveSectionContextType = {
activeSection: SectionName;
setActiveSection : React.Dispatch<React.SetStateAction<SectionName>>
};

const ActiveSectionContext = createContext<ActiveSectionContextType | null>(null);

export default function ActiveSectionContextProvider({
children,
}: ActiveSectionContextProviderProps) {
const [activeSection, setActiveSection ] = useState<SectionName>("Home");

return (
<ActiveSectionContext.Provider
value={{
activeSection,
setActiveSection,
}}
>
{children}
</ActiveSectionContext.Provider>

)
}

export function useActiveSectionContext() {
const context = useContext(ActiveSectionContext);

if (context === null) {
throw new Error(
"useActiveSectionContext must be used within an ActiveSectionContextProvider"
);
}

return context;
}

Add ActiveSectionContextProvider to Layout

Modify Layout.tsx:

1
2
3
4
5
6
...
<ActiveSectionContextProvider>
<Header />
{children}
</ActiveSectionContextProvider>
...

Consume context

Modify Intro.tsx:

1
2
3
4
5
6
7
8
9
10
const { ref, inView} = useInView({
threshold: 0.75
});
const { setActiveSection } = useActiveSectionContext();

useEffect(()=>{
if( inView) {
setActiveSection("Home");
}
},[inView, setActiveSection]);

Modify About.tsx:

1
2
3
4
5
6
7
8
9
10
11
export default function About() {
const { ref, inView} = useInView({
threshold: 0.75
});
const { setActiveSection } = useActiveSectionContext();

useEffect(()=>{
if( inView) {
setActiveSection("About");
}
},[inView, setActiveSection]);

Modify Projects.tsx:

1
2
3
4
5
6
7
8
9
10
const { ref, inView} = useInView({
threshold: 0.5
});
const { setActiveSection } = useActiveSectionContext();

useEffect(()=>{
if( inView) {
setActiveSection("Projects");
}
},[inView, setActiveSection]);

Add more control to make movement smoothly

Modify ActiveSectionContext.tsx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type ActiveSectionContextType = {
activeSection: SectionName;
setActiveSection : React.Dispatch<React.SetStateAction<SectionName>>
timeOfLastClick: number;
setTimeOfLastClick: React.Dispatch<React.SetStateAction<number>>;
};

const ActiveSectionContext = createContext<ActiveSectionContextType | null>(null);

export default function ActiveSectionContextProvider({
children,
}: ActiveSectionContextProviderProps) {
const [activeSection, setActiveSection ] = useState<SectionName>("Home");
// We need to keep track of this to disable temporarily when user clicks on a link
const [timeOfLastClick, setTimeOfLastClick] = useState(0);

return (
<ActiveSectionContext.Provider
value={{
activeSection,
setActiveSection,
timeOfLastClick,
setTimeOfLastClick
}}
>
{children}
</ActiveSectionContext.Provider>

)
}

Modify Head.tsx:

1
2
3
4
5
6
7
8
9
<Link 
className={`flex w-full items-center justify-center px-3 py-3 hover:text-gray-950
transition ${activeSection === link.name? "text-gray-950":""}`}
href={link.hash}
onClick={()=> {
setActiveSection(link.name);
setTimeOfLastClick(Date.now()); //Log Click time
}}
>

Modify Intro.tsx/Abount.tsx/Projects.tsx:

1
2
3
4
5
useEffect(()=>{
if( inView && Date.now() - timeOfLastClick > 1000 ) {
setActiveSection("Home");
}
},[inView, setActiveSection,timeOfLastClick]);

Demo

Demo

请我喝杯咖啡吧~

支付宝
微信