🎩 CSS를 활용한 Parallax Scolling은 원근법(perspective)에 의한 방식이다.
렌즈 구경(24, 50, 135mm...)과 카메라와 피사체 간의 거리를 고려하면 이해가 빠르다.
🔥 카메라를 들고 좌우 혹은 상하로 움직일 때, 거리에 따라 오브젝트의 움직임을 생각해 보자.
📸 카메라의 움직임에 따라 피사체가 카메라에 가까울수록 빠르게, 멀수록 느리게 움직인다!!! 이 점을 생각하고 Z포지션을 조정해 보자.
- 전경
front-layer
: 카메라에서 제일 가까운 피사체.- 중경
mid-layer
: 중간쯤에 있는 피사체.- 후경(원경)
back-layer
: 배경과 같이 멀~리 있는 피사체.
🏃♂️ 편의상 Z포지션이 0인 레이어(중경)를 mid-layer
로 설정하고, 후경에 레이어back-layer
를 배치해보자.
후경 레이어는 앞에 있는 레이어보다 움직임이 느리게 나타난다.
# html
<div class=para-wrapper>
<header class="intro">Header</header>
<div class="para-group" id="group-1">
<div class="para-layer back-layer">Back Layer</div>
<div class="para-layer mid-layer">Mid Layer</div>
</div>
<footer class="outro">Footer</footer>
</div>
# css
.para-wrapper {
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
/* 페이지 전체를 감싼 div에 perspective속성을 추가해야 한다. */
perspective: 300px;
}
.intro,
.outro {
height: 100vh;
background: royalblue;
display: flex;
justify-content: center;
align-items: center;
}
.para-group {
position: relative;
height: 80vh;
transform-style: preserve-3d;
z-index: -1;
}
#group-1 .para-layer {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
}
#group-1 .back-layer {
color: white;
transform: translate3d(0, 0, -300px) scale(2.1);
background: url(./image.jpg) no-repeat center / 100%;
}
⛈ 위치상 후경에 배치된 요소는 당연히 작게 나타난다. 이를 원래 크기로 바꾸기 위해 아래 수식을 적용하면 된다.
scaleRecover = 1 + ( tranlateZ * -1) / wrapper perspective
.back-layer { transform: translate3d(0, 0, -300px) scale(2); background: url(./image.jpg) no-repeat center / cover; }
🎩 자바스크립틀를 이용해서 Parallax 이펙트를 만들려면 DOM에서 윈도우와 스크롤 관련 데이터를 사용하야 한다.
# html
<header class="placeholder">Header</header>
<section class="para-section">
<div class="para-content">Parallax Moving</div>
</section>
<footer class="placeholder">Footer</footer>
# css
<style>
.placeholder {
height: 150vh;
background: royalblue;
display: flex;
justify-content: center;
align-items: center;
}
.para-section {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
background-image: url(./image.jpg);
background-size: 100%;
background-position: 50% 50%;
background-repeat: no-repeat;
}
</style>
- 현재 윈도우의 높이:
window.innerHeight
- 문서가 현재 스크롤 된 Y위치:
window.scrollY
- 효과를 적용할 요소의 높이:
element.offsetHeight
- 효과를 적용할 요소의 Y위치:
element.offsetTop
💫 이미지 요소가 화면 정중앙에 위치 했을 때를 0
으로, 상단으로 빠져나가는 지점을 -1
, 하단에 출현하는 지점을 1
로 만들어주는 식을 구해보자.
yOffsetRatio
-> -1 ~ +1 을 반환한다.const elementHeight = paraSection.offsetHeight;
let centerOffest = window.scrollY - paraSection.offsetTop;
let yOffsetRatio = centerOffest / elementHeight;
✨ 이미지 사이즈에 따라 픽셀px
단위의 포지션 이동은 활용도가 떨어지므로, background-position-Y
를 %
를 활용해 포지션을 움직여 준다. 이때 포지션 50%
를 중심으로 움직인다는걸 잊지말자.
paraSection.style.backgroundPositionY = `${50 + yOffsetRatio * 100}%`;
✌️ 움직임의 속도와 방향을 컨트롤 하기위해 increment
를 곱해주면 더 좋을 것 같다.
const increment = -1;
let yOffset = 50 + yOffsetRatio * 100 * increment;
paraSection.style.backgroundPositionY = `${yOffset}%`;
addEventListener()
를 사용해 보자.윈도우의 스크롤 이벤트를 받아 parallaxScroll
함수를 실행.
# js
const paraSection = document.querySelector('.para-section');
const parallaxScroll = () => {
const elementHeight = paraSection.offsetHeight;
const increment = -1;
let centerOffest = window.scrollY - paraSection.offsetTop;
let yOffsetRatio = centerOffest / elementHeight;
let yOffset = 50 + yOffsetRatio * 100 * increment;
console.log(yOffset);
paraSection.style.backgroundPositionY = `${yOffset}%`;
};
window.addEventListener('scroll', parallaxScroll);
console.log("scrolled")
를 찍어보면 알겠지만 스크롤 이벤트 리스너는 수많은 "scrolled"
를 콘솔창에 찍을 것이다. 이거 맘에 안듬💢
그래서 요소가 윈도우에 나타났을때만 스크롤 이벤트가 실행되도록 Intersection Observer API
를 사용하기로 했다.💥
const options = { rootMargin: "-100px" };
const paraObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
window.addEventListener("scroll", parallaxScroll);
console.log("ON");
} else {
window.removeEventListener("scroll", parallaxScroll);
console.log("OFF");
}
});
}, options);
paraObserver.observe(paraSection);
이상 Parallax Scroll 이펙트를 만드는 CSS버전과 JS버전 2가지를 알아봤다.
근데 GSAP이 애니메이션에 좋다던데... 한번 알아봐야겠다!