스크롤에 따른 목차 하이라이트 만들기

2020. 7. 31. 16:33공부내용 공유하기

프로젝트 내에서 웹 프론트엔드 포지션을 맡고, 처음 한 과제 중 하나는 howto 페이지였다.

 

과제 목표는 inteliJ 페이지처럼 만드는 것.

 

 

IntelliJ IDEA overview - Help | IntelliJ IDEA

IntelliJ IDEA overview IntelliJ IDEA is an Integrated Development Environment (IDE) for JVM languages designed to maximize developer productivity. It does the routine and repetitive tasks for you by providing clever code completion, static code analysis, a

www.jetbrains.com

아주 깔끔하게 되어있는 부분을 알 수 있다.

 

반응형이나, 좌측 링크는 어렵지 않았는데, 왼쪽 목차를 하이라이트 시키는 부분에서 난관이 좀 있었다.

 

스크롤에 따라 현재 위치를 인식시키고, 위치에 맞는 목차를 강조처리 하면 되는 부분이었는데,

 

당연하게도 목차를 누르면 해당 위치로 스크롤링이 되어야했다.

 

거기까진 문제가 없었는데...

 

onScroll에 이벤트 바인딩이 되어있다 보니, 현재 목차에서 타겟 목차 위치로 이동하면서 그 사이에 있는 목차들도 같이 하이라이트가 되는 현상이었다.

 

부드럽게 이동한다? 뭐 이런 느낌일 수도 있는데 실제로 보면 좀 많이 거슬린다.

 

목차 클릭시 경로에 있는 아이템들도 강조되는걸 볼 수 있다

 

이전에 작업했을 때에는 jQuery로 쉽게 해결했다.

 

$('#btn').animate({left:400}, {
  duration: 3000,
  easing:'linear',
  
  complete: function() {
   //완료되었을 때
  },
  
  step: function(now, fx) {
   //진행중일 때
  }
})

jQuery에서는 스크롤 애니메이션을 animate라는 메소드로 설정했는데, complete와 step을 통해 애니메이션의 상태값에 따른 동작을 넣을 수 있었다.

 

간단하게 스크롤에 따라 하이라이트 되는 메소드에 if(변수)를 하나 넣고,

 

step 상태에서는 변수 = false, complete 상태에서는 변수 = true로 세팅하면 끝이었다.

 

 

그런데 지금 하는 React 환경에서는 jQuery를 굳이 가져와서 사용하고 싶지도 않고, React에서 할 수 있는 방법을 알아야겠다는 생각이 들어서 공유한다.

 

 constructor(props) {
    super(props)

    this.state = {
      active: '', //활성화된 목차 이름
      scrolling: false, //스크롤중인지 아닌지 state에 저장
    }
    
    this.timer = null // 타이머의 초기값은 null로 넣어놓는다. 
    // 초기값이라는 것을 확인하기 위한 부분이라 뭘로 넣어도 상관없음. ex. -1, 0, 아무거나 가능
  }

먼저 컴포넌트 생성자에서 스크롤중인지 아닌지를 확인할 변수를 선언하고 state에 넣는다.

 

귀찮지만 timeout을 넣을 객체도 하나 필요하다. 나는 timer라는 이름으로 만들고 초기값으로 null을 넣었다.

 

  //현재 위치 파악해서 active바꿈
  checkPosition = (pos) => {
    if (!this.state.scrolling) {
    //state에 저장된 scrolling 값으로 동작 유무 판별
    
        //   ...
	// 현재 위치값 (pos)을 파악해서 어떤 목차를 하이라이트할지 찾는 코드 부분
	//   ...
    
      this.setState({ active: key })
      //하이라이트를 할 목차가 뭔지 알려준다
    }
  }

 

현재 위치를 찾아서 하이라이트 하는 코드에, 조건문을 하나 추가해서 동작 여부를 scrolling변수로 컨트롤한다.

 

  scrollEventHandle = () => {
   // 스크롤 이벤트 핸들러를 만들어서 아이템 클릭시에 호출시킴 
    if (this.timer !== null) {
      clearTimeout(this.timer)
    }
    // 타이머 첫 호출이 아니면, 타이머를 초기화시킴
    // (이미 생성된 타이머 객체를 초기화 하는 방법으로, 타이머들이 겹치는 문제 발생 방지)
    this.timer = setTimeout(() => {
      this.setState({ scrolling: false })
    }, 800)
    // 스크롤 이벤트가 끝난 후 0.8초 후에 스테이트에 스크롤이 멈췄다는 정보 전달
    // 스크롤이 계속된다면, 타이머가 계속 초기화되면서 scrolling은 false가 되지 못함
  }


  //타임라인 아이템 클릭시 해당 칸으로 넘어감
  handleClick = (id) => {
    this.setState({ active: id, scrolling: true })
    // 아이템 클릭과 동시에, 하이라이트 된 아이템 정보와 스크롤링이 시작되었다는 정보 저장
	
    // ... 타겟 포지션으로 이동하는 코드 생략
    
    this.scrollEventHandle()
    //위에서 만든 메소드를 호출한다.
  }

이렇게 하고 나면, 스크롤이 멈춘 후 0.8초 후에나 스크롤 위치를 통한 목차 하이라이트 이벤트가 활성화 된다.

 

과연 결과는?

 

잘 된다