nav bar 클릭시 스크롤 이동 로직

미연·2022년 1월 6일
1
post-thumbnail

아이디어

클래스명에 '.clicked_nav' 라는 단어가 더해지면, 클릭한 해당 메뉴에 검정 밑줄이 생긴다!

  1. 특정 li 메뉴가 클릭되면 모든 메뉴에 붙인 ref를 순회하여, 해당 메뉴 클래스명 뒤에 .clicked_nav라는 단어가 붙은 클래스명 찾아 .clicked_nav 제거
  2. 클릭한 해당 메뉴 클래스명 뒤에 .clicked_nav 라는 단어를 추가
  3. switch ~ case를 활용하여, 클릭한 해당 메뉴의 영역에 따른 스크롤 이동

리팩토링 전

  • 하나의 메서드에 nav 클릭 기능클릭시 스크롤되는 기능, 이 두 가지 기능을 모두 넣었었다.
    -> 메서드의 분리가 필요했다. 하나의 메서드는 하나의 기능만 작동하도록 설계하는 SOLID - 단일 책임의 원칙 잊지 말자!!!
  • 반복되는 구간은 다른 메서드로 빼주어 호출하는 방식으로 리팩토링 했다.
  • for문에서 forEach문을 사용해 보았다.
 // clicked nav menu - border-bottom & scroll move method
  const changeNavItem = (e) => {
    // 클릭한 메뉴 밑줄 처리를 위한 ref 리스트
    const navRefList = [classIntroduceRef, classReviewRef, classCurriculumRef, classCommunityRef, refundPolicyRef]

    // 각 메뉴 항목의 스크롤 이동 기준
    const introOffsetTop = document.querySelector('.class_info_box').offsetTop
    const reviewOffsetTop = document.querySelector('.review_header').offsetTop
    const curriculumOffsetTop = document.querySelector('.curriculum_box').offsetTop
    const communityOffsetTop = document.querySelector('.class_community').offsetTop
    const refundOffsetTop = document.querySelector('.refund_policy_box').offsetTop

    // ref로 전체 nav 메뉴 밑줄 처리 초기화
    for ( let i = 0 ; i < navRefList.length ; i++ ) {
    	if (navRefList[i].current.classList.value.split(' ')[1] === 'clicked_nav' )
 		   navRefList[i].current.classList.remove('clicked_nav')
    }

    // 클릭한 nav 메뉴만 밑줄 처리
    e.target.classList.add('clicked_nav')

    // 클릭한 해당 nav 메뉴로 스크롤 이동 
     switch (e.target.classList.value.split(' ')[0]) {
       // 클릭한 nav 메뉴가 '리뷰'일 경우
      case 'class_review_nav' :
        if (e.target.offsetParent.classList.contains('fixed')) {
          window.scrollTo({ top: reviewOffsetTop - 52, behavior: 'smooth' })
        } else {
          window.scrollTo({ top: reviewOffsetTop - 200, behavior: 'smooth' })
        }
        break;

      // 클릭한 nav 메뉴가 '커리큘럼'일 경우
      case 'class_curriculum_nav' :
        if (e.target.offsetParent.classList.contains('fixed')) {
          window.scrollTo({ top: curriculumOffsetTop - 52, behavior: 'smooth' })
        } else {
          window.scrollTo({ top: curriculumOffsetTop - 200, behavior: 'smooth' })
        }
        break;

      // 클릭한 nav 메뉴가 '커뮤니티'일 경우
      case 'class_cummunity_nav' :
        if (e.target.offsetParent.classList.contains('fixed')) {
          window.scrollTo({ top: communityOffsetTop - 52, behavior: 'smooth' })
        } else {
          window.scrollTo({ top: communityOffsetTop - 200, behavior: 'smooth' })
        }
        break;

      // 클릭한 nav 메뉴가 '환불정책'일 경우 
      case 'refund_policy_nav' :
        if (e.target.offsetParent.classList.contains('fixed')) {
          window.scrollTo({ top: refundOffsetTop - 52, behavior: 'smooth' })
        } else {
          window.scrollTo({ top: refundOffsetTop - 200, behavior: 'smooth' })
        }
        break;

      // nav 메뉴 기본값 '강의 소개'를 클릭했을 경우
      default :
      if (e.target.offsetParent.classList.contains('fixed')) {
        window.scrollTo({ top: introOffsetTop - 52, behavior: 'smooth' })
      } else {
        window.scrollTo({ top: introOffsetTop - 200, behavior: 'smooth' })
      }
    }
  }

리팩토링 1차

  // clicked nav menu - border-bottom & scroll move method
  const changeNavItem = (e) => {
    // 클릭한 메뉴 밑줄 처리를 위한 ref 리스트
    const navRefList = [classIntroduceRef, classReviewRef, classCurriculumRef, classCommunityRef, refundPolicyRef]

    // 각 메뉴 항목의 스크롤 이동 기준
    const introOffsetTop = document.querySelector('.class_info_box').offsetTop
    const reviewOffsetTop = document.querySelector('.review_header').offsetTop
    const curriculumOffsetTop = document.querySelector('.curriculum_box').offsetTop
    const communityOffsetTop = document.querySelector('.class_community').offsetTop
    const refundOffsetTop = document.querySelector('.refund_policy_box').offsetTop

    // ref로 전체 nav 메뉴 밑줄 처리 초기화
    navRefList.forEach(ref => {
      if (ref.current.classList.value.split(' ')[1] === 'clicked_nav' )
      ref.current.classList.remove('clicked_nav')
    })

    // 클릭한 nav 메뉴만 밑줄 처리
    e.target.classList.add('clicked_nav')

    // 클릭한 해당 nav 메뉴의 클래스명을 판별하는 메서드
     switch (e.target.classList.value.split(' ')[0]) {
       // 클릭한 nav 메뉴가 '리뷰'일 경우
      case 'class_review_nav' :
        e.target.offsetParent.classList.contains('fixed') ?
          clickedNavScroll(reviewOffsetTop, 52)
          : clickedNavScroll(reviewOffsetTop, 200)
        break;

      // 클릭한 nav 메뉴가 '커리큘럼'일 경우
      case 'class_curriculum_nav' :
        e.target.offsetParent.classList.contains('fixed') ? 
        clickedNavScroll(curriculumOffsetTop, 52)
        : clickedNavScroll(curriculumOffsetTop, 200)
        break;

      // 클릭한 nav 메뉴가 '커뮤니티'일 경우
      case 'class_cummunity_nav' :
        e.target.offsetParent.classList.contains('fixed') ?
        clickedNavScroll(communityOffsetTop, 52)
        : clickedNavScroll(communityOffsetTop, 200)
        break;

      // 클릭한 nav 메뉴가 '환불정책'일 경우 
      case 'refund_policy_nav' :
        e.target.offsetParent.classList.contains('fixed') ?
        clickedNavScroll(refundOffsetTop, 52)
        : clickedNavScroll(refundOffsetTop, 200)
        break;

      // nav 메뉴 기본값 '강의 소개'를 클릭했을 경우
      default :
        e.target.offsetParent.classList.contains('fixed') ?
        clickedNavScroll(introOffsetTop, 52)
        : clickedNavScroll(introOffsetTop, 200)
    }   
  }

  // 클릭한 메뉴로 스크롤 이동 메서드
  const clickedNavScroll = (offset, num) => {
      return window.scrollTo({ top: `${offset}` - `${num}`, behavior: 'smooth' })
  }

리팩토링 2차

// clicked nav menu - border-bottom & scroll move method
const changeNavItem = (e) => {
  // 클릭한 메뉴 밑줄 처리를 위한 ref 리스트
  const navRefList = [classIntroduceRef, classReviewRef, classCurriculumRef, classCommunityRef, refundPolicyRef]  

  // nav 안의 메뉴를 ref로 순회하여, 전체 nav 메뉴 밑줄 처리 초기화
  navRefList.forEach(ref => {
    if (ref.current.classList.value.includes('clicked_nav'))
      ref.current.classList.remove('clicked_nav')
  })

  // 클릭한 nav 메뉴만 밑줄 처리
  e.target.classList.add('clicked_nav')

  // nav 고정 여부에 따라 달라지는 스크롤 이동 처리
  e.target.offsetParent.classList.contains('fixed') ?
    clickedNavScroll(moveToOffsetTop(e), 52) : clickedNavScroll(moveToOffsetTop(e), 200)
}

// 각 nav 메뉴 위치에 따른 offsetTop을 return하는 메서드
const moveToOffsetTop = (e) => {
  // 각 메뉴 항목의 스크롤 이동 기준
  const introOffsetTop = document.querySelector('.class_info_box').offsetTop
  const reviewOffsetTop = document.querySelector('.review_header').offsetTop
  const curriculumOffsetTop = document.querySelector('.curriculum_box').offsetTop
  const communityOffsetTop = document.querySelector('.class_community').offsetTop
  const refundOffsetTop = document.querySelector('.refund_policy_box').offsetTop    

  switch (e.target.classList.value.split(' ')[0]) {
    // 클릭한 nav 메뉴가 '리뷰'일 경우
    case 'class_review_nav' :
      return reviewOffsetTop

    // 클릭한 nav 메뉴가 '커리큘럼'일 경우
    case 'class_curriculum_nav' :
      return curriculumOffsetTop

    // 클릭한 nav 메뉴가 '커뮤니티'일 경우
    case 'class_cummunity_nav' :
      return communityOffsetTop

    // 클릭한 nav 메뉴가 '환불정책'일 경우 
    case 'refund_policy_nav' :
      return refundOffsetTop

    // nav 메뉴 기본값 '강의 소개'를 클릭했을 경우
    default :
      return introOffsetTop
  }
}

// 클릭한 메뉴로 스크롤 이동 메서드
const clickedNavScroll = (offset, num) => {
  return window.scrollTo({ top: `${offset}` - `${num}`, behavior: 'smooth' })
} 

배운 점

1. ref를 배열로 관리하고 싶을 때, 데이터타입을 신경 쓰자

const navStringList = ['classIntroduceRef', 'classReviewRef', 'classCurriculumRef', 'classCommunityRef', 'refundPolicyRef'];

console.log(typeof navStringList);
// string

const navRefList = [classIntroduceRef, classReviewRef, classCurriculumRef, classCommunityRef, refundPolicyRef];

console.log(typeof navRefList);
// object

2. 메서드 하나에는 하나의 기능만 넣도록 하자.

3. 중복되는 코드는 얼마든지 짧게 줄일 수 있다.

사고방식을 전환하여 줄이는 방법을 택해 보자. 아자아자 빠이팅🔥

profile
FE Developer

0개의 댓글