Skip to content

Commit 427c2d1

Browse files
committed
added custom hook 'useIntersectionObserver' to modify url hash when a section is in view. Nav scrolls to the appropriate section on click, smoothly. Portfolio <-> blog animates the change in nav smoothly. selected section is highlighted with hueRotation. other css tweaks
1 parent 81b5fed commit 427c2d1

19 files changed

+230
-56
lines changed

src/components/HVContent.tsx

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { useEffect, useState } from "react";
1+
import { MutableRefObject, useContext, useEffect, useState } from "react";
22
import { animated, useTransition } from "react-spring";
33
import "./styles/HVContent.css";
44
import Deerfall from "./projects/Deerfall";
55
import MediaMatchup from "./projects/MediaMatchup";
66
import Introduction from "./Introduction";
7-
import mediaIcon from "../img/navIcons/media.png";
8-
import techIcon from "../img/navIcons/tech.png";
9-
import aboutIcon from "../img/navIcons/about.png";
10-
import blogIcon from "../img/navIcons/blog.png";
117
import HVSideNav from "./HVSideNav";
8+
import AppContext from "../contexts/AppContext";
9+
import { useIntersectionObserver } from "../hooks/useIntersectionObserver";
10+
import { useLocation, useNavigate } from "react-router-dom";
1211

1312
interface Props {
1413
project: string;
@@ -19,6 +18,36 @@ interface Props {
1918
const HVContent = ({ project, isIntro, allParams }: Props) => {
2019
// - - - - STATES - - - -
2120
const [isPortfolio, setIsPortfolio] = useState<boolean>(true);
21+
const location = useLocation();
22+
const navigate = useNavigate();
23+
// - - - - CONTEXT - - - -
24+
const { scrollRefs, hueRotation } = useContext(AppContext);
25+
const observerOptions: IntersectionObserverInit = {
26+
threshold: 0.5,
27+
root: null,
28+
rootMargin: "0px",
29+
};
30+
const mediaScrollObserver = useIntersectionObserver(
31+
scrollRefs.media,
32+
observerOptions,
33+
false
34+
);
35+
const techScrollObserver = useIntersectionObserver(
36+
scrollRefs.tech,
37+
observerOptions,
38+
false
39+
);
40+
const blogScrollObserver = useIntersectionObserver(
41+
scrollRefs.blog,
42+
observerOptions,
43+
false
44+
);
45+
const aboutScrollObserver = useIntersectionObserver(
46+
scrollRefs.about,
47+
observerOptions,
48+
false
49+
);
50+
2251
// - - - - PROJECTS - - - -
2352
const allProjList = {
2453
intro: <Introduction />,
@@ -42,6 +71,8 @@ const HVContent = ({ project, isIntro, allParams }: Props) => {
4271
// exitBeforeEnter: true,
4372
});
4473

74+
// = = = = = FUNCTIONS = = = = =
75+
4576
const checkAndSetProjComp = () => {
4677
if (isIntro) {
4778
setLocalProject("intro");
@@ -50,7 +81,11 @@ const HVContent = ({ project, isIntro, allParams }: Props) => {
5081
}
5182
};
5283

53-
// - - - - - USE EFFECTS - - - - -
84+
const scrollToElement = (ref: React.MutableRefObject<any>) => {
85+
ref.current?.scrollIntoView({ behavior: "smooth" });
86+
};
87+
88+
// = = = = = USE EFFECTS = = = = =
5489
// Hide Project when it is Changed, then set the new project once hidden
5590
useEffect(() => {
5691
setShow(false);
@@ -71,9 +106,40 @@ const HVContent = ({ project, isIntro, allParams }: Props) => {
71106
}
72107
}, [allParams]);
73108

109+
useEffect(() => {
110+
if (mediaScrollObserver?.isIntersecting) {
111+
navigate(`${location.pathname}#media`);
112+
}
113+
}, [mediaScrollObserver?.isIntersecting]);
114+
115+
useEffect(() => {
116+
if (techScrollObserver?.isIntersecting)
117+
navigate(`${location.pathname}#tech`);
118+
}, [techScrollObserver?.isIntersecting]);
119+
120+
useEffect(() => {
121+
if (blogScrollObserver?.isIntersecting)
122+
navigate(`${location.pathname}#blog`);
123+
}, [blogScrollObserver?.isIntersecting]);
124+
125+
useEffect(() => {
126+
if (aboutScrollObserver?.isIntersecting)
127+
navigate(`${location.pathname}#about`);
128+
}, [aboutScrollObserver?.isIntersecting]);
129+
74130
return (
75131
<main className='HVContent'>
76-
{isIntro ? "" : <HVSideNav isPortfolio={isPortfolio} />}
132+
{isIntro ? (
133+
""
134+
) : (
135+
<HVSideNav
136+
isPortfolio={isPortfolio}
137+
allParams={allParams}
138+
scrollRefs={scrollRefs}
139+
hueRotation={hueRotation}
140+
scrollToElement={scrollToElement}
141+
/>
142+
)}
77143

78144
{transitionFade(
79145
(styles, item) =>

src/components/HVSideNav.tsx

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,72 @@ import techIcon from "../img/navIcons/tech.png";
44
import aboutIcon from "../img/navIcons/about.png";
55
import blogIcon from "../img/navIcons/blog.png";
66
import { animated, useSpring } from "react-spring";
7-
import { useContext } from "react";
8-
import AppContext from "../contexts/AppContext";
7+
import { useEffect, useState } from "react";
8+
import { ScrollRefs, HueRotation } from "../models/Models";
99

1010
interface Props {
11+
scrollRefs: ScrollRefs;
12+
hueRotation: HueRotation;
1113
isPortfolio: boolean;
14+
allParams: string[];
15+
scrollToElement: (ref: React.MutableRefObject<any>) => void;
1216
}
1317

14-
const HVSideNav = ({ isPortfolio }: Props) => {
15-
const { scrollRefs } = useContext(AppContext);
18+
const HVSideNav = ({
19+
isPortfolio,
20+
allParams,
21+
scrollRefs,
22+
hueRotation,
23+
scrollToElement,
24+
}: Props) => {
25+
const [hlNav, setHLNav] = useState("media");
1626
const navSizeChange = useSpring({
1727
from: { height: "150px" },
1828
to: { height: "110px" },
1929
config: { mass: 1, tension: 200, friction: 15 },
2030
reverse: isPortfolio,
2131
});
2232

23-
const handleScroll = (ref: any) => {
24-
ref.current?.scrollIntoView({ behavior: "smooth" });
33+
const handleNavClick = (
34+
ref: React.MutableRefObject<any>,
35+
navName: string
36+
) => {
37+
scrollToElement(ref);
38+
setHLNav(navName);
2539
};
2640

41+
useEffect(() => {
42+
scrollRefs.media.current?.scrollIntoView({ behavior: "smooth" });
43+
setHLNav("media");
44+
}, [allParams[1]]);
45+
2746
return (
2847
<animated.nav className='HVSideNav' style={navSizeChange}>
2948
{isPortfolio ? (
3049
<ul>
3150
<li>
32-
<img
33-
onClick={() => handleScroll(scrollRefs.media)}
51+
<animated.img
52+
onClick={() => handleNavClick(scrollRefs.media, "media")}
3453
className='nav-icon'
54+
style={hlNav === "media" ? hueRotation : {}}
3555
src={mediaIcon}
3656
alt='media navigation icon'
3757
/>
3858
</li>
3959
<li>
40-
<img
41-
onClick={() => handleScroll(scrollRefs.tech)}
60+
<animated.img
61+
onClick={() => handleNavClick(scrollRefs.tech, "tech")}
4262
className='nav-icon'
63+
style={hlNav === "tech" ? hueRotation : {}}
4364
src={techIcon}
4465
alt='technologies and skills navigation icon'
4566
/>
4667
</li>
4768
<li>
48-
<img
49-
onClick={() => handleScroll(scrollRefs.about)}
69+
<animated.img
70+
onClick={() => handleNavClick(scrollRefs.about, "about")}
5071
className='nav-icon'
72+
style={hlNav === "about" ? hueRotation : {}}
5173
src={aboutIcon}
5274
alt='about project navigation icon'
5375
/>
@@ -56,17 +78,19 @@ const HVSideNav = ({ isPortfolio }: Props) => {
5678
) : (
5779
<ul>
5880
<li>
59-
<img
60-
onClick={() => handleScroll(scrollRefs.media)}
81+
<animated.img
82+
onClick={() => handleNavClick(scrollRefs.media, "media")}
6183
className='nav-icon'
84+
style={hlNav === "media" ? hueRotation : {}}
6285
src={mediaIcon}
6386
alt='media navigation icon'
6487
/>
6588
</li>
6689
<li>
67-
<img
68-
onClick={() => handleScroll(scrollRefs.blog)}
90+
<animated.img
91+
onClick={() => handleNavClick(scrollRefs.blog, "blog")}
6992
className='nav-icon'
93+
style={hlNav === "blog" ? hueRotation : {}}
7094
src={blogIcon}
7195
alt='media navigation icon'
7296
/>

src/components/projects/subComponents/ProjDescPortfolio.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ const ProjDescPortfolio = ({
2121
className='ProjDescPortfolio full-w-h
2222
'>
2323
<section ref={tech_ScrollRef} className='content-section tech-skills-ctr'>
24-
Tech and Skills
24+
<p>Tech and Skills</p>
2525
</section>
2626
<section ref={about_ScrollRef} className='content-section about-ctr'>
27-
About Project
27+
<p>About Project</p>
2828
</section>
2929
</animated.div>
3030
);

src/components/projects/subComponents/ProjImage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const ProjImage = ({
1616
<div className='content-section full-w-h'>
1717
<picture>
1818
<source srcSet={imgSrc} />
19-
<img className='main-img' src={imgSrc_Fallback} alt={imgAltTxt} />
19+
<img src={imgSrc_Fallback} alt={imgAltTxt} />
2020
</picture>
2121
</div>
2222
</section>

src/components/projects/subComponents/Project.css

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ li {
5353
}
5454

5555
.Project .content-section {
56+
display: flex;
57+
flex-direction: column;
58+
justify-content: center;
59+
align-items: center;
5660
box-sizing: border-box;
5761
height: 100%;
5862
width: 100%;
@@ -73,13 +77,11 @@ li {
7377
position: relative;
7478
}
7579

76-
.Project .ProjImage .content-section {
77-
box-sizing: border-box;
78-
border: 1px black dashed;
79-
/* padding-bottom: 35%; */
80+
.Project .ProjImage .content-section picture {
81+
height: 100%;
8082
}
8183

82-
.Project .ProjImage .content-section .main-img {
84+
.Project .ProjImage .content-section picture img {
8385
height: 75%;
8486
}
8587

src/components/styles/HVSideNav.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
width: 45px;
1616
/* styling */
1717
color: white;
18-
background-color: rgba(96, 96, 96, 0.5);
18+
background-color: rgba(96, 96, 96, 0.3);
1919
backdrop-filter: blur(4px);
2020
font-size: 40px;
2121
border: 2px white solid;
@@ -30,11 +30,15 @@
3030

3131
.HVSideNav .nav-icon {
3232
image-rendering: pixelated;
33-
filter: invert();
33+
filter: saturate(0) brightness(100);
3434
margin: 8px;
3535
width: 30px;
3636
}
3737

38+
.HVSideNav .highlighted-nav {
39+
filter: none;
40+
}
41+
3842
.HVSideNav .highlighted-icon {
3943
background-color: red;
4044
}

src/components/styles/Introduction.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ p {
5151
flex-direction: column;
5252
align-items: center;
5353
width: 100%;
54-
max-height: 63%;
5554
overflow: scroll;
5655
z-index: 1;
5756
color: rgb(250, 250, 250);
5857
padding: 6vh 0%;
58+
margin-bottom: 150px;
5959
}
6060

6161
.Introduction .img-ctr {

src/contexts/AppContext.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createContext, useState } from "react";
1+
import { createContext, useContext } from "react";
2+
import { HueRotation, ScrollRefs } from "../models/Models";
23

34
export interface AppContextModel {
45
isMobile: boolean;
@@ -8,13 +9,24 @@ export interface AppContextModel {
89
scrollRefs: any;
910
}
1011

11-
const defaultValue: AppContextModel = {
12-
isMobile: true,
12+
const defaultValues: AppContextModel = {
13+
isMobile: false,
1314
hueRotation: undefined,
1415
hueRotation_Inv: undefined,
1516
setHueDuration: undefined,
1617
scrollRefs: undefined,
1718
};
1819

19-
const AppContext = createContext(defaultValue);
20+
const AppContext = createContext(defaultValues);
21+
// const initAppContext = createContext<AppContextModel | undefined>(undefined);
22+
23+
// export const AppContext = () => {
24+
// const useAppContext = useContext(initAppContext!);
25+
// if (!initAppContext) {
26+
// throw new Error("No AppContext.Provider found when calling AppContext.");
27+
// } else {
28+
// return useAppContext!;
29+
// }
30+
// };
31+
2032
export default AppContext;

src/contexts/AppContextProvider.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import AppContext from "./AppContext";
22
import { ReactNode, useEffect, useRef, useState } from "react";
3-
import { useSpring } from "react-spring";
3+
import { SpringValue, useSpring } from "react-spring";
44
import AppConfig from "../AppConfig.json";
5+
import { HueRotation, ScrollRefs } from "../models/Models";
56

67
interface Props {
78
children: ReactNode;
@@ -16,7 +17,7 @@ const AppContextProvider = ({ children }: Props) => {
1617
const tech = useRef(null);
1718
const about = useRef(null);
1819
const blog = useRef(null);
19-
const [scrollRefs, setScrollRefs] = useState({
20+
const [scrollRefs, setScrollRefs] = useState<ScrollRefs>({
2021
media,
2122
tech,
2223
about,
@@ -27,7 +28,7 @@ const AppContextProvider = ({ children }: Props) => {
2728
const [hueDuration, setHueDuration] = useState<number>(
2829
AppConfig.hueAnimDuration
2930
);
30-
const hueRotation = useSpring({
31+
const hueRotation: HueRotation = useSpring({
3132
loop: { reverse: true, config: { duration: hueDuration } },
3233
to: {
3334
filter: "hue-rotate(130deg) saturate(80%) sepia(30%)",
@@ -37,7 +38,7 @@ const AppContextProvider = ({ children }: Props) => {
3738
},
3839
config: { duration: hueDuration, precision: 0.001 },
3940
});
40-
const hueRotation_Inv = useSpring({
41+
const hueRotation_Inv: HueRotation = useSpring({
4142
loop: { reverse: true, config: { duration: hueDuration } },
4243
to: {
4344
filter: "hue-rotate(0deg) saturate(100%) sepia(0%)",

src/contexts/AuthContextProvider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { User } from "firebase/auth";
22
import AuthContext from "./AuthContext";
33
import { ReactNode, useEffect, useState } from "react";
44
import { auth } from "../firebaseConfig";
5-
import UserAccount from "../models/UserAcount";
5+
import { UserAccount } from "../models/Models";
66
import { createNewUser, getUserById } from "../services/userService";
77

88
interface Props {

0 commit comments

Comments
 (0)