이번에는 포트폴리오 사이트 내 Career 섹션을 구현한 과정을 정리했다. 단순히 텍스트를 나열하는 것보다, 시간 흐름에 따라 중앙선을 기준으로 좌우 번갈아 등장하는 타임라인 형식을 채택했고, 스크롤 시 등장 애니메이션을 넣어 더욱 직관적이고 시각적으로 흥미롭게 구성했다.
학력, 동아리, 프로젝트 등 다양한 이력을 한눈에 보기 쉽게 나열
스크롤에 반응하는 등장 애니메이션 적용
좌우 번갈아 정렬된 카드 + 중앙선 + 마커 표시
Career.tsx는 타임라인 전체 섹션
CareerTimeline.tsx는 반복 렌더링 컴포넌트
careerData.ts는 이력 데이터 배열
CareerItem은 개별 타임라인 카드 구성
IntersectionObserver를 구현해서 요소가 화면에 보일 때 애니메이션이 적용되도록 처리했다.
const [visible, setVisible] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => setVisible(entry.isIntersecting),
{ threshold: 0.1 }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
이렇게 설정된 visible 상태를 기준으로 Tailwind 클래스를 동적으로 바꿔서 애니메이션을 적용했다:
className={`transition-all duration-700 transform
${visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"}`}
↔️ 좌우 번갈아 정렬
카드가 번갈아 왼쪽/오른쪽으로 배치되도록 index 값을 기준으로 정렬 방향을 결정했다.
const isLeft = index % 2 === 0;
/* 타임라인 중앙선 */
<div className="absolute left-1/2 transform -translate-x-1/2 h-full w-[2px] bg-indigo-300 z-0" />
/* 각 항목 위치의 원 */
<div className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-indigo-500 rounded-full border-4 border-white z-10" />
이 마커가 카드와 중앙선을 시각적으로 연결시켜주며, 흐름을 따라가기가 훨씬 쉬워졌다.
이번 작업에서는 라이브러리를 쓰지 않고 직접 인터랙션을 구현하면서, 상태 관리와 뷰포트 트리거 처리에 대한 이해가 더 깊어졌다.
IntersectionObserver는 코드도 간단하면서 성능에도 부담이 적어서 앞으로도 자주 쓸 것 같다.
특히 좌우 교차 배치와 타임라인 마커는 UI적으로도 시각적 재미를 주기 때문에, 단순 이력 나열보다 훨씬 더 강렬한 인상을 줄 수 있었다.