[javascript] 세로 스크롤 슬라이드 구현

hyejinJo·2023년 12월 6일
1

Javascript / jQuery

목록 보기
6/8
  • 이미 풀페이지 스크롤이 구현되어있는 상태
  • 특정 페이지내에서 슬라이드가 세로로 스크롤되는 구조로 만들어야 하는데, 풀페이지 스크롤 기능과 겹쳐 스크롤 이벤트를 구현하는데 쉽지 않았다..
  • 해당 페이지에 도달하면 body 의 스크롤을 막은 후 슬라이드를 실행하게 하고, 슬라이드가 끝나면 다시 body 의 스크롤이 실행되게끔 하는 구조로 만들어보았다.
<main id="contents" style="max-width: 100%">
	<div class="page1">페이지1</div>
	<div class="page2">페이지2</div>
	<div class="page3">
		<h2>페이지3</h2>
		<div class="swiper-container">
      <div class="swiper-wrapper">
        <div class="swiper-slide active">Slide 1</div>
        <div class="swiper-slide">Slide 2</div>
        <div class="swiper-slide">Slide 3</div>
      </div>
      <div class="pagination-test"></div>
    </div>
	</div>
	<div class="page4">페이지4</div>
</main>
.page3 {position: relative; }
.page3 .swiper-slide {position: absolute; width: 100%; top: 0; left: 0; height: 100vh; opacity: 0; transition: .3s;}
.page3 .swiper-slide.active {opacity: 1;}
.page3 .swiper-slide:nth-child(1) {background-color: darksalmon}
.page3 .swiper-slide:nth-child(2) {background-color: green}
.page3 .swiper-slide:nth-child(3) {background-color: gold}

페이지에 도달시 body 스크롤 막기

  • 슬라이드가 있는 페이지의 위치(offsetTop) 에 도달하면 body 의 스크롤 이벤트 막기
  • 슬라이드가 끝나면 removeEventListener 를 통해 이벤트 제거
// 스크롤 이벤트(페이지)

const page_03 = document.querySelector('.page3');
const page_03Top = page_03.offsetTop;

window.addEventListener('scroll', function() {
  const windowY = window.scrollY;
  setTimeout(() => {
    if (sdCut_08Top <= windowY && windowY <= sdCut_08Top + 100) {
      // 스크롤 방지
      $body.addEventListener('wheel', preventScroll, { passive: false });
      console.log('dd')
    }
    if (!sdCut_08.classList.contains('currentActive')) {
      $body.removeEventListener('wheel', preventScroll, { passive: false });
    }
  }, 500)
});

마우스휠 이벤트로 슬라이드 조정

  • 마우스휠 이벤트의 내장기능인 deltaY 를 사용하여 휠의 방향에 따라 기능을 줄 수 있었다.
  • 아래로 슬라이드 시 가장 마지막 슬라이드로 도달하면 body 스크롤 풀기
  • 위로 슬라이드 시 가장 첫번째 슬라이드로 도달하면 body 스크롤 풀기
// 마우스휠 이벤트(슬라이드)

let activeIndex = 0; // [0, 1, 2]
const $body = document.querySelector('body');
const slideArr = Array.from(document.querySelectorAll('.swiper-slide'));

sdCut_08.addEventListener('mousewheel', function(e) {
  if (e.deltaY > 0) { // 마우스 휠 down
    if (activeIndex !== slideArr.length - 1) {
      activeIndex = slideArr.findIndex((slide) => slide.classList.contains('active'))
      slideArr[activeIndex].classList.remove('active')
      slideArr[activeIndex + 1].classList.add('active')
      activeIndex = activeIndex + 1;
      console.log('down')
    } else {
      $body.removeEventListener('wheel', preventScroll, { passive: false });
    }
  } else if (e.deltaY < 0) { // 마우스 휠 up
    if (activeIndex !== 0) {
      activeIndex = slideArr.findIndex((slide) => slide.classList.contains('active'))
      slideArr[activeIndex].classList.remove('active')
      slideArr[activeIndex - 1].classList.add('active')
      activeIndex = activeIndex - 1;
      console.log('up')
    } else {
      $body.removeEventListener('wheel', preventScroll, { passive: false });
    }
  }
})

throttle 로 성능 최적화

  • 과도하게 발생되는 스크롤(’scroll’) 이벤트와 마우스휠(’mousewheel’)이벤트는 throttle 로 처리해주었다. ⇒ throttle: 특정 시간 동안 한 번만 함수를 실행하도록 하는 기능을 제공
  • throttle 함수를 먼저 정의해준 뒤 이벤트 핸들러에 적용
function _throttle(callback, delay) {
  let timeoutId;
  return function() {
    if (!timeoutId) {
      // 지정된 시간이 지나면 콜백을 실행하고 timeoutId를 재설정
      timeoutId = setTimeout(function() {
        callback.apply(null, arguments);
        timeoutId = null;
      }, delay);
    }
  };
}

🚨 문제 발생 & 해결

하지만 스크롤 이벤트에는 잘 작동되어서, 마우스휠 이벤트(슬라이드) 이벤트에도 똑같이 적용해줬더니 다음과 같은 에러가 발생했다.

const throttledMousewheelHandler = _throttle(function(e) {
	console.log(e) // undefined
  if (e.deltaY > 0) { // 마우스 휠 down
    ...
  } else if (e.deltaY < 0) { // 마우스 휠 up
    ...
  }
}, 500);

처음 적용한 스크롤 이벤트에는 이벤트객체(e) 를 사용하지 않았고, 두번째 함수에서는 이벤트객체를 사용하여 e 가 undefined 로 나타난것이었다.

그래서 아래와같이 throttle 의 콜백함수에 이벤트 객체를 인수로 넣어준 후 다시 작동시켰더니 잘 작동이 되었다.

return function(...args)

// throttle 함수 정의
function _throttle(callback, delay) {
  let timeoutId;
  return function(...args) { // ...args 에 이벤트 객체(e)가 들어감
    if (!timeoutId) {
      timeoutId = setTimeout(function() {
        callback.apply(null, args);
        timeoutId = null;
      }, delay);
    }
  };
}

최종 코드:

  • 화면이 resize 되었을 때도 동작되게끔 내용을 추가
// script

// 슬라이드 스크롤 ----------------------------
  // throttle 함수 정의
  function _throttle(callback, delay) { // delay 에는 속도가 들어감
    let timeoutId;
    return function(...args) { // ...args 에 이벤트 객체(e)가 들어감
      if (!timeoutId) {
        // 지정된 시간이 지나면 콜백을 실행하고 timeoutId를 재설정
        timeoutId = setTimeout(function() {
          callback.apply(null, args);
          timeoutId = null;
        }, delay);
      }
    };
  }

  let activeIndex = 0; // [0, 1, 2]
  const $body = document.querySelector('body');
  const slideArr = Array.from(document.querySelectorAll('.swiper-slide'));
  const page_03 = document.querySelector('.page3');
  const page_03Top = page_03.offsetTop;

  function preventScroll(e) {
    e.preventDefault();
  }

  // 스크롤 이벤트 핸들러
  const throttledScrollHandler = _throttle(function() {
    const windowY = window.scrollY;
    if (page_03Top <= windowY && windowY <= page_03Top + 100) {
      // 스크롤 방지
      $body.addEventListener('wheel', preventScroll, { passive: false });
      console.log('dd');
    }
    if (!page_03.classList.contains('currentActive')) {
      $body.removeEventListener('wheel', preventScroll, { passive: false });
    }
  }, 500);
  window.addEventListener('scroll', throttledScrollHandler);

  const throttledMousewheelHandler = _throttle(function(e) {
    if (e.deltaY > 0) { // 마우스 휠 down
      if (activeIndex !== slideArr.length - 1) {
        activeIndex = slideArr.findIndex((slide) => slide.classList.contains('active'))
        slideArr[activeIndex].classList.remove('active')
        slideArr[activeIndex + 1].classList.add('active')
        activeIndex = activeIndex + 1;
        console.log('down')
      } else {
        $body.removeEventListener('wheel', preventScroll, { passive: false });
      }
    } else if (e.deltaY < 0) { // 마우스 휠 up
      if (activeIndex !== 0) {
        activeIndex = slideArr.findIndex((slide) => slide.classList.contains('active'))
        slideArr[activeIndex].classList.remove('active')
        slideArr[activeIndex - 1].classList.add('active')
        activeIndex = activeIndex - 1;
        console.log('up')
      } else {
        $body.removeEventListener('wheel', preventScroll, { passive: false });
      }
    }
  }, 500);

  page_03.addEventListener('mousewheel', throttledMousewheelHandler);

	// 화면이 resize 되었을 때
	window.addEventListener('resize', function() {
    sdCut_08Top = sdCut_08.offsetTop;
    window.addEventListener('scroll', throttledScrollHandler);
    sdCut_08.addEventListener('mousewheel', throttledMousewheelHandler);
  });

  // 풀페이지 스크롤
  function startScrollFullPage() {
    //풀페이지 스크롤되는 코드 
  }
  startScrollFullPage()
profile
FE Developer 💡

0개의 댓글