logo

DowanKim

9. 저 이만큼 작업했으니 저 뽑아주세요! (뭘 보라는건데 지원자놈아)

2025년 11월 22일

포트폴리오 사이트

트러블슈팅 : Featured 프로젝트 캐러셀 자동 슬라이드 구현하기

1. 문제 상황

포트폴리오 사이트를 한상곤 교수님께 보여드렸고, 피드백을 주셨습니다:

"프로젝트가 너무 많고, 너가 메인으로 삼고 싶은 프로젝트가 무엇인지 정하는게 좋겠다."

문제점:

  • 프로젝트가 많아 핵심이 없음
  • 사용자가 어떤 프로젝트를 먼저 봐야 할지 불명확
  • 대표 프로젝트를 강조할 방법 필요
  • 지원자 뽑는 면접관 분들이 얼마나 바쁜데 내 작업물 하나하나 다 자세하게 보시고 친히 들어가 보시고 깃허브도 봐주시고 같은 낙관적인 생각

해결 방향:

  • 캐러셀을 통해 대표 프로젝트를 강조
  • 자동 슬라이드로 시선 유도
  • 사용자가 클릭하고 싶게 구현

2. 구현 목표

  1. 자동 슬라이드: 페이지 진입 시 자동으로 프로젝트 순환
  2. 수동 제어: 사용자가 버튼이나 인디케이터로 이동 가능
  3. 자연스러운 전환: 수동 조작 후 자동 슬라이드 재개
  4. 시각적 효과: 현재 슬라이드 강조, 다음 슬라이드 힌트 제공

3. 구현: 자동 슬라이드와 수동 제어의 조화

3.1 상태 관리

const [currentIndex, setCurrentIndex] = useState(0) const [isAutoPlaying, setIsAutoPlaying] = useState(true) const intervalRef = useRef<NodeJS.Timeout | null>(null)

상태 설명:

  • currentIndex: 현재 표시 중인 슬라이드 인덱스
  • isAutoPlaying: 자동 재생 여부 (수동 조작 시 일시 정지)
  • intervalRef: setInterval의 ID를 저장 (cleanup용)

3.2 자동 슬라이드 구현

useEffect(() => { if (isAutoPlaying) { intervalRef.current = setInterval(() => { setCurrentIndex((prev) => (prev + 1) % projects.length) }, 8000) } return () => { if (intervalRef.current) { clearInterval(intervalRef.current) } } }, [isAutoPlaying, projects.length])

핵심 로직:

  1. isAutoPlayingtrue일 때만 setInterval 실행
  2. 8초마다 currentIndex를 1 증가
  3. % projects.length로 순환 (마지막 슬라이드 다음은 첫 번째)
  4. cleanup에서 clearInterval로 메모리 누수 방지

useRef를 사용하는 이유:

  • setInterval의 반환값(ID)을 저장해야 cleanup에서 정확히 제거 가능
  • useState로 저장하면 렌더링을 유발하므로 useRef 사용

3.3 수동 제어와 자동 재생 재개

const goToSlide = (index: number) => { setCurrentIndex(index) setIsAutoPlaying(false) setTimeout(() => setIsAutoPlaying(true), 5000) }

동작 원리:

  1. 슬라이드 이동: setCurrentIndex(index)
  2. 자동 재생 일시 정지: setIsAutoPlaying(false)
  3. 5초 후 자동 재생 재개: setTimeout(() => setIsAutoPlaying(true), 5000)

isAutoPlaying 플래그가 필요한가?

만약 setInterval만 사용한다면:

// 문제가 있는 코드 useEffect(() => { const interval = setInterval(() => { setCurrentIndex((prev) => (prev + 1) % projects.length) }, 8000) return () => clearInterval(interval) }, [])

문제점:

  • 사용자가 수동으로 슬라이드를 이동해도 8초 후 자동으로 다음 슬라이드로 넘어감
  • 사용자가 읽는 중에 갑자기 전환되어 방해됨

해결:

  • isAutoPlaying 플래그로 자동 재생 제어
  • 수동 조작 시 일시 정지하고, 일정 시간 후 재개

3.4 좌우 이동 함수

const goToPrevious = () => { goToSlide((currentIndex - 1 + projects.length) % projects.length) } const goToNext = () => { goToSlide((currentIndex + 1) % projects.length) }

순환 로직:

  • goToPrevious: 현재 인덱스에서 1 감소, 음수가 되면 마지막 인덱스로
  • goToNext: 현재 인덱스에서 1 증가, 마지막을 넘으면 첫 번째로

4. 슬라이드 배치와 시각 효과

4.1 Transform을 이용한 슬라이드 이동

<div className="flex transition-transform duration-500 ease-in-out" style={{ transform: `translateX(calc(${(100 - 85) / 2}% - ${currentIndex * 85}% - ${currentIndex * 3}rem))`, }} > {projects.map((project, index) => ( <div key={index} className="relative flex-shrink-0" style={{ width: '85%', marginRight: '3rem', }} > {/* 슬라이드 내용 */} </div> ))} </div>

Transform 계산 분석:

  1. 초기 오프셋: (100 - 85) / 2 = 7.5%

    • 슬라이드 폭이 85%이므로 양쪽에 7.5%씩 여백
    • 첫 슬라이드가 중앙에 오도록 오프셋
  2. 슬라이드 이동: - ${currentIndex * 85}%

    • 각 슬라이드가 85% 폭이므로 인덱스 × 85%만큼 이동
  3. 마진 보정: - ${currentIndex * 3}rem

    • 슬라이드 간 marginRight: 3rem을 고려한 보정

예시:

  • currentIndex = 0: translateX(7.5%) → 첫 슬라이드 중앙
  • currentIndex = 1: translateX(7.5% - 85% - 3rem) → 두 번째 슬라이드 중앙
  • currentIndex = 2: translateX(7.5% - 170% - 6rem) → 세 번째 슬라이드 중앙

4.2 시각적 효과

현재 슬라이드 강조:

className={`object-cover transition-all duration-500 rounded-2xl ${ index === currentIndex ? 'scale-100 brightness-100' : 'scale-95 brightness-85' } group-hover:scale-105`}

효과:

  • 현재 슬라이드: scale-100, brightness-100 (100% 크기, 밝기)
  • 다른 슬라이드: scale-95, brightness-85 (95% 크기, 85% 밝기)
  • 호버 시: scale-105 (105% 크기로 확대)

의도:

  • 현재 슬라이드를 강조
  • 다른 슬라이드로 "다음 콘텐츠가 있다"는 힌트 제공
  • 호버 시 인터랙션 피드백

Gradient Overlay:

<div className="absolute inset-0 bg-gradient-to-t from-black/40 via-black/10 to-transparent rounded-2xl" />

효과:

  • 하단에서 상단으로 그라데이션 (검은색 → 투명)
  • 텍스트 가독성 향상
  • 이미지 위 텍스트가 잘 보이도록

텍스트 투명도:

<div className={`absolute bottom-0 left-0 right-0 p-8 md:p-12 text-white transition-opacity duration-500 ${ index === currentIndex ? 'opacity-100' : 'opacity-50' }`}>

효과:

  • 현재 슬라이드: opacity-100 (완전 불투명)
  • 다른 슬라이드: opacity-50 (반투명)

5. UI 요소: 버튼과 인디케이터

5.1 좌우 화살표 버튼

<button onClick={goToPrevious} className="absolute left-4 top-1/2 -translate-y-1/2 z-10 bg-white/50 hover:bg-white/70 text-black p-3 rounded-full transition-all duration-300 backdrop-blur-sm" aria-label="이전 프로젝트" > <i className="fa-solid fa-chevron-left text-xl"></i> </button>

특징:

  • 절대 위치: absolute left-4 top-1/2 -translate-y-1/2 (왼쪽 중앙)
  • 반투명 배경: bg-white/50 (50% 투명도)
  • 호버 효과: hover:bg-white/70 (70% 투명도)
  • 접근성: aria-label 제공

5.2 인디케이터

<div className="flex justify-center gap-2 mt-6"> {projects.map((_, index) => ( <button key={index} onClick={() => goToSlide(index)} className={`h-2 rounded-full transition-all duration-300 ${ index === currentIndex ? 'w-8 bg-[#03e8f9]' : 'w-2 bg-white/40 hover:bg-white/60' }`} aria-label={`프로젝트 ${index + 1}로 이동`} /> ))} </div>

특징:

  • 현재 인디케이터: w-8 (넓게), bg-[#03e8f9] (강조 색상)
  • 다른 인디케이터: w-2 (좁게), bg-white/40 (반투명)
  • 호버 효과: hover:bg-white/60
  • 클릭 가능: 특정 슬라이드로 바로 이동

6. HCI 관점에서의 설계 결정

6.1 자동 전환 주기: 8초

일반적인 캐러셀은 3~5초 주기를 사용하지만, 포트폴리오 사이트 특성상 8초로 설정했습니다.

이유:

  • 프로젝트 설명을 읽을 시간 필요
  • 너무 빠르면 내용 파악 어려움
  • 8초면 충분히 읽고 다음으로 넘어갈 수 있음

6.2 수동 조작 후 재개 시간: 5초

사용자가 수동으로 슬라이드를 이동하면 5초 후 자동 재생을 재개합니다.

이유:

  • 사용자가 조작했다는 것은 관심이 있다는 신호
  • 5초면 충분히 확인할 수 있음
  • 너무 길면 자동 재생의 의미가 없어짐

6.3 시각적 힌트: brightness와 peek 효과

다음 슬라이드를 미리 보여주는 peek 효과:

index === currentIndex ? 'scale-100 brightness-100' : 'scale-95 brightness-85'

효과:

  • 사용자가 "다음 콘텐츠가 있다"는 것을 인지
  • 스크롤 없이도 여러 프로젝트 확인 가능
  • 클릭 유도

7. 전체 동작 흐름

1. 페이지 진입
   ↓
2. 자동 슬라이드 시작 (8초 간격)
   ↓
3. 사용자가 화살표 버튼 클릭
   ↓
4. goToSlide 호출
   → currentIndex 변경
   → isAutoPlaying = false (자동 재생 일시 정지)
   → 5초 후 isAutoPlaying = true (자동 재생 재개)
   ↓
5. 5초 후 자동 슬라이드 재개
   ↓
6. 8초마다 자동으로 다음 슬라이드로 이동
   ↓
7. 마지막 슬라이드 다음은 첫 번째 슬라이드로 순환

8. 마무리

  1. 대표 프로젝트 강조: 핵심 프로젝트를 명확히 전달
  2. 사용자 경험 향상: 자동 슬라이드와 수동 제어의 자연스러운 조화
  3. 시각적 효과: 현재 슬라이드 강조와 peek 효과로 다음 콘텐츠 힌트 제공
  4. 접근성: 버튼과 인디케이터로 다양한 방식의 제어 가능

캐러셀 자동 슬라이드를 구현하며, 제가 생각하는 메인 프로젝트 네가지를 프로젝트 섹션 상단에 크게 나타냈습니다. 이와 동시에, 너무 무의미한(그냥 하루만에 한 작업물들) 작업물들은 삭제하고, 저라는 사람을 잘 나타낼 수 있는 작업물들만 남겼습니다.

이렇게 작업하고 싱글벙글 한상곤 교수님께 이렇게 수정했습니다 교수님 하며 자랑도 했습니다. 훨씬 낫다고 하시네요..

역시 시니어 개발자 분들은 경력이 쌓이며 HCI 적인 측면이나 다양한 기술 지식을 기반으로 나오는 사용성 아이디어들이 대단하신 것 같습니다.

다양한 기술을 아는 만큼 뭔가 이렇게 해결할 수 있지 않을까 라는, 시야가 저희보다 훨씬 넓으시지 않나 생각합니다. 나도 저렇게 되고 싶당.(언젠가 저렇게 될거야 라는 생각으로 바쁘고 귀찮아도 작업 회고를 하는...)