[jQuery] 페이지 첫 화면에서 메인 콘텐츠 부분으로 자동 스크롤하기

첫 팀 프로젝트로 멤버 소개 페이지를 만들기위해 레퍼런스를 찾아보다가 단색 배경에 크게 이름을 적어둔 포트폴리오를 발견했다.

저런 형식으로 접속시 첫 화면에 꽉차게 한 눈에 들어오는 소개를 보여준 뒤 자세한 소개가 보이는 곳으로  자동 스크롤 하고싶다는 생각이 떠올랐다.

 

최종 결과

버튼을 클릭했을 때 자동 스크롤
일정 위치까지 스크롤했을 때 자동스크롤

처음엔 버튼을 눌렀을 때 자동 스크롤되는 기능만 구현했다가, 버튼을 누르지 않고 스크롤 하는 사람들도 자동 스크롤로 넘어가게 하고싶어서 기능을 추가했다.

그러다가.. 한참을 여기에 매달리게되고... 괜한 욕심을 부렸나싶어서 조금 후회했다가 그래도 해보고싶어서 열심히 고민에 고민을 거쳐보았다..

 

 

해결한 문제들

스크롤을 조금 내린 상태에서 버튼 클릭

처음 시작할 때 버튼 클릭으로만 내려가는 것을 가정했다보니 위치를 이동시키는 함수에 시작 위치를 따로 넘겨주지 않았다 (0으로 가정함)

그러다보니 코드를 읽으면서도 크게 문제점을 못느꼈는데, 차라리 const START_POSITION = 0 등으로 선언해두고 사용했다면 더 빨리 인지하지 않았을까? 싶기도하다

간단하게 버튼을 클릭했을 때 실행되는 이벤트에서 startPosition = window.scrollY로 받아와 원하는 도착점까지 남은 거리를 계산하는 데에 사용했다.

 

스크롤 내려가는 중 무한 이벤트 실행

const titleOffsetBottom = $('.titleContainer').offset().top + $('.titleContainer').outerHeight(true);
$('.scrollDownArrow').click(function () {
  smoothScrollTo(titleOffsetBottom);
});

const scrollPoint = 350;
 $(window).scroll(function () {
  if (window.scrollY > scrollPoint) {
    smoothScrollTo(titleOffsetBottom);
  }
});

처음엔 이렇게 스크롤 버튼이 클릭됐을 때, 스크롤이 발생했고 스크롤 위치가 지정한 위치를 넘어섰을 때 바로 지정한 위치(titleOffsetBotto)으로 이동시키는 함수를 호출했다.

버튼은 이상이 없었지만 스크롤에 의한 이벤트가 실행됐을 때는 모든 순간이 window.scrollY > scrollPoint 였기에 반복해서 smoothScrollTo()가 호출되고 출발 위치를 반복해서 다시 잡는 현상이 발생했다.

 

위치만으로 조건을 판단하기엔 문제가 있다고 생각되어 튜터님께 조언을 구했고, 지금이 자동스크롤이 가능한 상황인지 판단하는 또다른 변수를 만들자고 제안을 주셨다.

 

isScrolling

smoothScrollTo()에 처음 진입했을 때 isScrolling = true로 변경해주고, true인 상태로 함수에 진입하면 바로 return 해줬다.

function smoothScrollTo(destination) {
  const duration = 1500;  // 내려가는데 걸리는 총 시간
  if (isScrolling) {
    return;
  } else {
    isScrolling = true;
  }
...
}

 

이렇게 하는 경우 isScrolling을 다시 false로 바꿔주는 구간이 없어 한번 하단으로 내려온 후에는 다시 자동스크롤로 내려올수 없다. 그래서 도착했을 때 isScrolling = false로 바꿔주었지만 위의 문제점과 마찬가지로 다시 window.scrollY > scrollPoint를 만족시키며 계속해서 smoothScrollTo()가 호출됐다.

 

버튼을 클릭하는 조건과 스크롤로 내려온 이후의 상황에 대해 다시 생각을 정리했고, 두 경우에 smoothScrollTo()로 이어지는 조건을 다르게 해줘야겠다 생각했다.

 

isArrived

조건이 되어줄 새로운 변수를 만들었다. 기존 isScrolling = false 로 바꿔주던 곳에서 isArrived = true로 변경하고 스크롤 이벤트는 !isArrived && scrollY > scrollPoint 두가지 조건을 모두 만족시킬 때 smoothScrollTo()로 이어지도록 변경했다.

 

let isScrolling = false;
let isArrived = false;

export function setScrollEvent() {
  const titleOffsetBottom = $('.titleContainer').offset().top + $('.titleContainer').outerHeight(true);
  $('.scrollDownArrow').click(function () {
    smoothScrollTo(titleOffsetBottom);
  });

  const scrollPoint = 350;
  $(window).scroll(function () {
    if (!isArrived && window.scrollY > scrollPoint) {
      smoothScrollTo(titleOffsetBottom);
    }
  });
}

function smoothScrollTo(destination) {
  if (isScrolling) {
    return;
  } else {
    isScrolling = true;
  }

  const duration = 300;
  let startTime = null;
  let startPosition;
  let distance;
  requestAnimationFrame(move);

  function move(currentTime) {
    if (startTime === null) {
      startTime = currentTime;
      startPosition = window.scrollY;
      distance = destination - startPosition;
    }

    let elapsedTime = currentTime - startTime;
    let nextPosition = startPosition + ease(elapsedTime, distance, duration);
    window.scrollTo(0, nextPosition);

    if (elapsedTime < duration) {
      requestAnimationFrame(move);
    }
    else {
      isScrolling = false;
      isArrived = true;
    }
  }
}

function ease(elapsedTime, distance, duration) {
  let timeRatio = elapsedTime / duration;
  let distanceRatio = distance * timeRatio * timeRatio;
  return distanceRatio;

 

이렇게 마무리.. 사실 시간이 조금씩 지날 때마다 너무 작은 부분에 고집을 부리고있는 건가하는 생각이 많이 들었는데 완성한 후에는 너무 뿌듯해서 또 다시 하길 잘했다는 생각이 들었다..ㅎㅎㅎ