[wesbos30] 13. 스크롤 시 이미지 슬라이드
💡 화면을 스크롤하면 이미지가 슬라이드 인하며 나타나는 기능을 구현해보자!
로직
- 화면 스크롤 인식
- 얼마나 스크롤해야 이미지가 슬라이드 인-아웃 될지 기준점 설정
코딩 과정
1. 스크롤 인식하는 핸들러
function debounce(func, wait = 20, immediate = true) {
var timeout
return function () {
var context = this,
args = arguments
var later = function () {
timeout = null
if (!immediate) func.apply(context, args)
}
var callNow = immediate && !timeout
clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) func.apply(context, args)
}
}
function checkSlide(e) {
console.log(window.scrollY)
}
window.addEventListener('scroll', debounce(checkSlide))
스크롤을 인식하기 위해서는 window에 이벤트핸들러를 할당해주면 된다.
이 때 window에 checkSlide 핸들러를 바로 할당해주고
핸들러에서 console.log(window.scrollY)를 출력해보면...
핸들러를 바로 할당해주면 매 스크롤마다 핸들러가 너무 많이 실행되므로
WesBos가 자체적으로 작성해둔 debounce 함수를 이용해 실행 빈도를 낮춘다.
2. 이미지 슬라이드 인-아웃 기준점 정하기
스크롤을 내리든 올리든 이미지 슬라이드 인-아웃 기능이 유연하게 작동하게 하기위해 기준점을 뷰포트 최하단과 뷰포트 최상단 두 가지로 나누어서 계산하도록 하자!
-
뷰포트 최하단 기준 계산
const sliderImages = document.querySelectorAll('.slide-in') function checkSlide() { sliderImages.forEach(sliderImage => { const imageHalf = window.scrollY + window.innerHeight const isViewBottomPast = imageHalf > sliderImage.offsetTop + sliderImage.height / 2 if (isViewBottomPast) { sliderImage.classList.add('active') } else { sliderImage.classList.remove('active') } }) }
위 그림을 통해 살펴보면
window.innerHeight는 윈도우 창틀을 포함하지않는 현재 뷰포트의 높이이며window.scrollY는document가 수직으로 얼마나 스크롤 됐는지를 픽셀 단위로 반환하는 값이다.⇒ 따라서 두 값을 더하면 현재 뷰포트 최하단의 절대위치를 알 수 있다.
이미지가 슬라이딩 인-아웃 하는 기준점을 이미지의 절반을 지날 때로 정하면
-
뷰포트 최하단 절대위치:
window.innerHeight+window.scrollY -
이미지 절반의 절대위치:
image.offsetTop+image.height / 2
뷰포트 최하단 절대위치가 더 크면 이미지가 보이고 작으면 이미지가 안보이도록 하면 된다.
-
-
뷰포트 최상단 기준 계산
이미지가 슬라이딩 인-아웃 하는 기준점을 이미지 최하단을 지날 때로 정하면
-
뷰포트 최상단 절대위치:
window.scrollY -
이미지 최하단 절대위치:
image.offsetTop+image.height
뷰포트 최상단 절대위치가 더 크면 이미지가 보이고 작으면 이미지가 안보이도록 한다.
function checkSlide() { sliderImages.forEach(sliderImage => { // 뷰포트 최상단 기준 const imageHalf = window.scrollY + window.innerHeight const isViewBottomPast = imageHalf > sliderImage.offsetTop + sliderImage.height / 2 // 뷰포트 최하단 기준 const imageBottom = sliderImage.offsetTop + sliderImage.height const isViewTopPast = window.scrollY < imageBottom if (isViewBottomPast && isViewTopPast) { sliderImage.classList.add('active') } else { sliderImage.classList.remove('active') } }) } -
최종 완성 코드
function debounce(func, wait = 20, immediate = true) {
var timeout
return function () {
var context = this,
args = arguments
var later = function () {
timeout = null
if (!immediate) func.apply(context, args)
}
var callNow = immediate && !timeout
clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) func.apply(context, args)
}
}
const sliderImages = document.querySelectorAll('.slide-in')
function checkSlide() {
sliderImages.forEach(sliderImage => {
// 뷰포트 최상단 기준
const imageHalf = window.scrollY + window.innerHeight
const isViewBottomPast =
imageHalf > sliderImage.offsetTop + sliderImage.height / 2
// 뷰포트 최하단 기준
const imageBottom = sliderImage.offsetTop + sliderImage.height
const isViewTopPast = window.scrollY < imageBottom
if (isViewBottomPast && isViewTopPast) {
sliderImage.classList.add('active')
} else {
sliderImage.classList.remove('active')
}
})
}
window.addEventListener('scroll', debounce(checkSlide))