https://www.youtube.com/watch?v=OPaLnMw2i_0&list=PL6QREj8te1P7gixBDSU8JLvQndTEEX3c3
어떤 스프린트를 해볼까하다가 Next를 좀 더 익숙하게 다뤄보고자 하는 마음에 클론코딩을 하나 더 해보기로 했다.
저번에 했던 포트폴리오 웹사이트인데 전혀 다른 구성과 디자인으로 해보았다.
일단 완성본부터 보자.
솔직히 유튜브 영상 썸네일이 너무 까리해보여서 선택하기는 했음.
SPA(single page application)이고 페이지 구성은
-헤더와 푸터
-Hero 섹션
-Projects 섹션
-Technology 섹션
-Timeline 섹션
- Accomplishments 섹션
헤더와 푸터는 그렇게 어려운 부분이 없었다.
컴포넌트를 좀더 잘게 쪼개어 헤더와 푸터 내부에서 Div1, Div2, DIv3로 나누어 구성하였다.
<Div2>
<li>
<Link href='#projects'>
<NavLink>Project</NavLink>
</Link>
</li>
<li>
<Link href='#tech'>
<NavLink>Technologies</NavLink>
</Link>
</li>
<li>
<Link href='#about'>
<NavLink>About</NavLink>
</Link>
</li>
</Div2>
이번에 처음 알게 된 건데 id를 href로 받았다.
이게 뭐지? 했는데
해당되는 id를 가진 섹션으로 페이지가 이동하게 되는 것이다.
예를 들어 Technologies 링크를 클릭하면
아래의 Technologies 섹션으로 자동스크롤 되면서 넘어간다.
이건 좀 신기방기
Hero 섹션
역시 컴포넌트들을 조합하여 위치만 조정해주면 되는데 여기서 새롭게 추가된 것
<Button
onClick={() => {
window.location = "https://google.com";
}}>
Learn More
</Button>
버튼에 window.location을 추가해주었다.
보통 html a 태그나 nextjs에서의 link 태그로 링크를 많이 걸어줬는데
button을 활용하여 window.location으로 링크를 걸어주었다.
Projects 섹션
<Section nopadding id='projects'>
<SectionDivider />
<SectionTitle main>Projects</SectionTitle>
<GridContainer>
{projects.map(({ image, title, description, tags, source, visit }) => {
return (
<BlogCard>
<Img src={image} />
<TitleContent>
<HeaderThree title>{title}</HeaderThree>
<Hr />
</TitleContent>
<CardInfo>{description}</CardInfo>
<div>
<TitleContent>Stack</TitleContent>
<TagList>
{tags.map((tag, i) => {
return <Tag key={i}>{tag}</Tag>;
})}
</TagList>
</div>
<UtilityList>
<ExternalLinks href={visit}>Code</ExternalLinks>
<ExternalLinks href={source}>Source</ExternalLinks>
</UtilityList>
</BlogCard>
);
})}
</GridContainer>
</Section>
BlogCard라는 컴포넌트로 map을 돌렸고 BlogCard 안에서 컴포넌트들을 조합해서 만들었다.
더미 데이터를 활용하여 projects를 만들어서 돌린건데
그 안에 프로젝트 주소와 소스 코드를 볼 수 있는 링크가 포함되어있는 object 형식으로 만들었다.
그래서 object 안의 visit과 source를 각각 link url로 받을 수 있도록 하였다.
그 외에는 별다른 특이사항 없음.
Technology 섹션
List로 만들어서 각각 아이콘이랑 글자만 따로 넣어주었음.
컴포넌트들 짬뽕해서 만든 거라 시간도 엄청 짧게 걸림.
Timeline 섹션
이게 제일 어려웠다.
사실 지금도 이해를 잘 못했다.
<CarouselContainer ref={carouselRef} onScroll={handleScroll}>
<>
{TimeLineData.map((item, index) => {
return (
<CarouselMobileScrollNode
key={index}
final={index === TOTAL_CAROUSEL_COUNT - 1}>
<CarouselItem
index={index}
id={`carousel_item-${index}`}
active={activeItem}
onClick={(event) => handleClick(event, index)}>
<CarouselItemTitle>
{item.year}
<CarouselItemImg
width='208'
height='6'
viewBox='0 0 208 6'
fill='none'
xmlns='http://www.w3.org/2000/svg'>
<path
fill-rule='evenodd'
clip-rule='evenodd'
d='M2.5 5.5C3.88071 5.5 5 4.38071 5 3V3.5L208 3.50002V2.50002L5 2.5V3C5 1.61929 3.88071 0.5 2.5 0.5C1.11929 0.5 0 1.61929 0 3C0 4.38071 1.11929 5.5 2.5 5.5Z'
fill='url(#paint0_linear)'
fill-opacity='0.33'
/>
<defs>
<linearGradient
id='paint0_linear'
x1='-4.30412e-10'
y1='0.5'
x2='208'
y2='0.500295'
gradientUnits='userSpaceOnUse'>
<stop stop-color='white' />
<stop
offset='0.79478'
stop-color='white'
stop-opacity='0'
/>
</linearGradient>
</defs>
</CarouselItemImg>
</CarouselItemTitle>
<CarouselItemText>{item.text}</CarouselItemText>
</CarouselItem>
</CarouselMobileScrollNode>
);
})}
</>
</CarouselContainer>
Carousel 형식으로 만들었는데 위의 부분이 Carousel의 데이터가 담겨있는 부분이다.
이렇게 숫자 + 별똥별 + 내용으로 구성되어있는 컴포넌트를 연도별로 map을 돌린 것이다.
이게 컴퓨터 모니터상으로는 이제 별 문제가 없다.
하지만 반응형을 안하면 섭하지.
그래서 모바일에서는 내용을 스크롤할 수 있도록 만들어주려고 했다.
const [activeItem, setActiveItem] = useState(0);
//useRef를 활용하여 carouselContainer으로 Dom으로 활용할 수 있도록 했다.
const carouselRef = useRef();
//선택된 노드를 왼쪽으로 스크롤하는 함수
const scroll = (node, left) => {
return node.scrollTo({ left, behavior: "smooth" });
};
//해당 아이템을 클릭하면 맨 왼쪽에 올 수 있도록 자동 스크롤. 없으면 그냥 스크롤기능만
const handleClick = (e, i) => {
e.preventDefault();
if (carouselRef.current) {
const scrollLeft = Math.floor(
carouselRef.current.scrollWidth * 0.7 * (i / TimeLineData.length)
);
scroll(carouselRef.current, scrollLeft);
}
};
//스크롤된 정도에 따라 중간에서 놓았을 때 넘어갈 위치 정해준다.
const handleScroll = () => {
if (carouselRef.current) {
const index = Math.round(
(carouselRef.current.scrollLeft /
(carouselRef.current.scrollWidth * 0.7)) *
TimeLineData.length
);
setActiveItem(index);
}
};
//화면 창이 바뀌면 다시 처음화면으로 돌아오게 만들기
useEffect(() => {
const handleResize = () => {
scroll(carouselRef.current, 0);
};
window.addEventListener("resize", handleResize);
}, []);
스크롤 기능을 넣어준 코드들이다. 각각의 설명은 코드 위에 있으니 참고.
<CarouselButtons>
{TimeLineData.map((item, index) => {
return (
<CarouselButton
key={index}
index={index}
active={activeItem}
onClick={(event) => handleClick(event, index)}>
<CarouselButtonDot active={activeItem} />
</CarouselButton>
);
})}
</CarouselButtons>
그리고 아이템들 중 어떤 것들에 해당되어있는지 알려주는 점 5개를 보여주었다.
이걸 말하는 거다.
그래서 5개의 아이템 중 focus가 몇번째에 되어있는지 위에서 useState로 불러온 activeItem으로 추적하고 그 index에 해당하는 점의 opacity와 scale을 키워 표시될 수 있도록 했다.
지금 회고하면서 조금 더 이해가 된 거 같긴 한데 혼자 하라하면 못할듯. 그래서 다시 봐야됨.
Accomplishments 섹션
걍 컴포넌트들 짬뽕
데이터 별로 map만 돌리면 끝.
그래서 완성한 결과는 이렇다.
모바일 디자인이 핵심이었기에 모바일로 스샷을 찍었다.
배운점
- 링크를 href가 아니라 window.location으로 걸어도 되는구나.(근데 둘의 차이점이 과연 뭘까?)
- 링크에서 url이 아니라 id로 href를 걸면 자동으로 그 section으로 찾아가는구나. (이건 진짜 신박했는데 왜 몰랐을까?)
- 모바일에서 양 옆으로 스크롤하는 것도 내장 메소드가 다 있다. 잘 활용하면 만들 수 있음.
아쉬운 점
-이번 클론코딩은 이미 디자인이 다 되어있는 컴포넌트에 각각의 component들을 끼워맞추기만 하면 되는 작업이었다. 그 래서 직접 css를 다루지는 못했다는 점이 좀 아쉬웠다.
- 처음 세팅되어있는 컴포넌트들이 진짜 많았어서 뭔지도 모르고 따라치기만 한 부분도 꽤 많았던 것 같다. 그래도 그 부분은 제외하고 페이지 구성의 매커니즘을 이해하는 데 포커싱을 두었다.
'웹 > 개인 프로젝트' 카테고리의 다른 글
HTML CSS JS로 크리스마스 카드 만들기 + 눈 내리는 효과 (0) | 2022.12.25 |
---|---|
주가조회 웹사이트 7/28 업데이트 기록 (sns 공유 기능 등등) (0) | 2022.07.28 |
7월 1주차 스프린트 회고 - 미국주식 주가조회 웹사이트 (0) | 2022.07.10 |