Leeyanggoo
[JS] 자바스크립트 패럴랙스 이펙트 1 !! Parallax Effect 1!! 본문
패럴랙스(Parallax)는 무엇인고?
패럴랙스(Parallax)는 물체의 위치나 각도에 따라 관찰하는 시각적인 변화를 말합니다. 이는 눈이 물체를 볼 때, 눈의 위치나 시선이 바뀌면 물체의 위치나 모양이 바뀌어 보이는 것을 의미합니다.
이 패럴랙스 효과를 웹 디자인에서 스크롤과 함께 사용하는 걸 '패럴랙스 스크롤링'이라고 합니다.
패럴랙스 스크롤링은 사용자가 스크롤링할 때 레이어들이 다른 속도로 움직이는 효과를 주어 페이지가 동적이고 입체감 있는 느낌을 줍니다.
일반적으로 CSS와 Javascript를 이용하여 구현하며, Javascript에서는 스크롤의 위치에 따라 레이어를 이동시키는 코드를 작성합니다.
코드를 작성해봅시다
HTML
<header id="header">
<h1>Javascript Parallax Effect07</h1>
<p>패럴럭스 이펙트 : 메뉴 효과</p>
<ul>
<li class="active"><a href="parallaxEffect01.html">1</a></li>
<li><a href="parallaxEffect02.html">2</a></li>
<li><a href="parallaxEffect03.html">3</a></li>
<li><a href="parallaxEffect04.html">4</a></li>
<li><a href="parallaxEffect05.html">5</a></li>
<li><a href="parallaxEffect06.html">6</a></li>
<li><a href="parallaxEffect07.html">7</a></li>
</ul>
</header>
<!-- //header -->
<nav class="parallax__nav">
<ul>
<li class="active"><a href="#section1">메뉴1</a></li>
<li><a href="#section2">메뉴2</a></li>
<li><a href="#section3">메뉴3</a></li>
<li><a href="#section4">메뉴4</a></li>
<li><a href="#section5">메뉴5</a></li>
<li><a href="#section6">메뉴6</a></li>
<li><a href="#section7">메뉴7</a></li>
<li><a href="#section8">메뉴8</a></li>
<li><a href="#section9">메뉴9</a></li>
</ul>
</nav>
<!-- //parallax__nav -->
<main id="main">
<div class="parallax__wrap">
<section id="section1" class="parallax__item">
<span class="parallax__item__num">01</span>
<h2 class="parallax__item__title">section1</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">첫 걸음은 할 수 있는 일을 시작하는 것이다. 그러면 가능성이 열릴 것이다.</p>
</section>
<!-- //section1 -->
<section id="section2" class="parallax__item">
<span class="parallax__item__num">02</span>
<h2 class="parallax__item__title">section2</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">다르다고 해서 다르게 실행해서는 안 됩니다. 단지 더 잘해야 할 뿐이죠.</p>
</section>
<!-- //section2 -->
<section id="section3" class="parallax__item">
<span class="parallax__item__num">03</span>
<h2 class="parallax__item__title">section3</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">좋은 피드백보다는 나쁜 피드백에 신경을 쓰고 그와 같은 피드백을 친구들에게 받을 수 있도록 노력해야 합니다.</p>
</section>
<!-- //section3 -->
<section id="section4" class="parallax__item">
<span class="parallax__item__num">04</span>
<h2 class="parallax__item__title">section4</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">당신이 할 수 있는 만큼 정말로 열심히 일하세요. 적어도 일주일에 80~100시간 가량 투자해야 합니다.</p>
</section>
<!-- //section4 -->
<section id="section5" class="parallax__item">
<span class="parallax__item__num">05</span>
<h2 class="parallax__item__title">section5</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">만약 당신이 아침에 일어나서 미래는 더 좋아질 것이라고 생각한다면, 그날은 더 밝은 날이 될 것이다.</p>
</section>
<!-- //section5 -->
<section id="section6" class="parallax__item">
<span class="parallax__item__num">06</span>
<h2 class="parallax__item__title">section6</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">실패를 겪지 않는다면 충분히 혁신적이지 않았다는 증거입니다.</p>
</section>
<!-- //section6 -->
<section id="section7" class="parallax__item">
<span class="parallax__item__num">07</span>
<h2 class="parallax__item__title">section7</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">어떤 일이 충분히 중요하다면, 가능성이 없어 보이더라도 해라.</p>
</section>
<!-- //section7 -->
<section id="section8" class="parallax__item">
<span class="parallax__item__num">08</span>
<h2 class="parallax__item__title">section8</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">계란을 한 바구니에 담아도 된다. 그 바구니 속에서 일어나는 일들을 컨트롤 할 수 있다면.</p>
</section>
<!-- //section8 -->
<section id="section9" class="parallax__item">
<span class="parallax__item__num">09</span>
<h2 class="parallax__item__title">section9</h2>
<figure class="parallax__item__imgWrap">
<div class="parallax__item__img"></div>
</figure>
<p class="parallax__item__desc">좋은 아이디어는 그것이 실현될 때까지는 항상 미친 짓이다.</p>
</section>
<!-- //section9 -->
</div>
</main>
<!-- //main -->
<aside class="parallax__info">
<div class="scroll">scrollTop : <span>0</span>px</div>
<div class="info">
<ul>
<li>#section1 offset() : <span class="offset1">0</span>px</li>
<li>#section2 offset() : <span class="offset2">0</span>px</li>
<li>#section3 offset() : <span class="offset3">0</span>px</li>
<li>#section4 offset() : <span class="offset4">0</span>px</li>
<li>#section5 offset() : <span class="offset5">0</span>px</li>
<li>#section6 offset() : <span class="offset6">0</span>px</li>
<li>#section7 offset() : <span class="offset7">0</span>px</li>
<li>#section8 offset() : <span class="offset8">0</span>px</li>
<li>#section9 offset() : <span class="offset9">0</span>px</li>
</ul>
</div>
</aside>
<!-- //parallax__info -->
<footer id="footer">
<a href="mailto:didrn94553@gmail.com">didrn94553@gmail.com</a>
</footer>
<!-- //footer -->
parallax__nav는 각 parallax section으로 이동하기 위한 내비게이션 역할을 합니다.
"a href=" 링크로 해당 section의 id을 할당하여 해당 section으로 이동합니다.
main의 parallax__wrap은 각 parallax section의 부모 div입니다.
각 section은 고유 id를 가지고 있으며, 나타낼 이미지(img)와 명언 정보를 담고 있습니다.
parallax__info는 스크롤의 위치(scrollTop)와 각 section의 위치 정보를 나타냅니다.
자바스크립트 코드를 이용해 해당 section의 좌표값을 넣어줍니다.
CSS
/* header */
#header {
position: absolute;
left: 20px;
top: 20px;
}
#header h1 {
margin-bottom: 0.3em;
}
#header ul {
margin-top: 0.6em;
}
#header li {
display: inline-block;
}
#header li a {
color: #fff;
border: 1px solid #fff;
display: inline-block;
width: 30px;
height: 30px;
line-height: 30px;
border-radius: 50%;
text-align: center;
}
#header li.active a {
background: #fff;
color:#000
}
/* footer */
#footer {
text-align: center;
padding: 100px 0;
}
#footer a {
color: #fff;
font-size: 14px;
}
#footer a:hover {
text-decoration: underline;
}
/* parallax__nav */
.parallax__nav {
position: fixed;
right: 20px;
top: 20px;
z-index: 2000;
background-color: rgba(0,0,0,0.4);
padding: 20px 30px;
border-radius: 50px;
}
.parallax__nav li {
display: inline;
margin: 0 5px;
}
.parallax__nav li a {
display: inline-block;
padding: 5px 20px;
text-align: center;
line-height: 30px;
color: #fff;
}
.parallax__nav li.active a {
background-color: #fff;
color: #000;
border-radius: 20px;
box-sizing: content-box;
}
/* parallax__wrap */
.parallax__wrap {
max-width: 1600px;
width: 98%;
margin: 0 auto;
/* background-color: rgba(255,255,255,0.1); */
}
.parallax__item {
width: 1000px;
max-width: 70vw;
margin: 30vw auto;
/* background-color: rgba(255,255,255,0.3); */
margin-right: 0;
position: relative;
padding-top: 8vw;
}
.parallax__item:nth-child(even) {
margin-left: 0;
text-align: right;
}
.parallax__item__num {
font-size: 35vw;
font-weight: 100;
font-family: "Lato";
position: absolute;
left: -5vw;
top: -16vw;
opacity: 0.07;
z-index: -2;
}
.parallax__item:nth-child(even) .parallax__item__num {
left: auto;
right: -5vw;
}
.parallax__item__title {
font-weight: bold;
}
.parallax__item__imgWrap {
width: 100%;
padding-bottom: 56.25%;
position: relative;
z-index: -1;
}
.parallax__item__img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
filter: saturate(0%);
transition: all 1s;
}
.parallax__item:nth-child(1) .parallax__item__img {
background-image: url(../img/parallaxEffect01-min.jpg);
}
.parallax__item:nth-child(2) .parallax__item__img {
background-image: url(../img/parallaxEffect02-min.jpg);
}
.parallax__item:nth-child(3) .parallax__item__img {
background-image: url(../img/parallaxEffect03-min.jpg);
}
.parallax__item:nth-child(4) .parallax__item__img {
background-image: url(../img/parallaxEffect04-min.jpg);
}
.parallax__item:nth-child(5) .parallax__item__img {
background-image: url(../img/parallaxEffect05-min.jpg);
}
.parallax__item:nth-child(6) .parallax__item__img {
background-image: url(../img/parallaxEffect06-min.jpg);
}
.parallax__item:nth-child(7) .parallax__item__img {
background-image: url(../img/parallaxEffect07-min.jpg);
}
.parallax__item:nth-child(8) .parallax__item__img {
background-image: url(../img/parallaxEffect08-min.jpg);
}
.parallax__item:nth-child(9) .parallax__item__img {
background-image: url(../img/parallaxEffect09-min.jpg);
}
.parallax__item__desc {
font-size: 3vw;
line-height: 1.4;
margin-top: -5vw;
margin-left: -4vw;
word-break: keep-all;
}
.parallax__item:nth-child(even) .parallax__item__desc {
margin-left: auto; /* 마진 초기화 할 때는 auto */
margin-right: -4vw;
}
.parallax__info {
position: fixed;
left: 20px;
bottom: 20px;
background-color: rgba(0,0,0,0.6);
color: #fff;
padding: 20px;
border-radius: 10px;
font-size: 14px;
line-height: 1.4;
}
.parallax__info .scroll {}
.parallax__info {}
@media (max-width: 1200px){
.parallax__nav {
padding: 10px;
background-color: rgba(0,0,0,0.9);
right: 10px;
left: 10px;
top: 10px;
border-radius: 5px;
text-align: center;
}
.parallax__nav li {
margin: 2px;
}
.parallax__nav li a {
font-size: 12px;
padding: 0 14px;
}
}
header 및 footer는 페이지의 상하단에 타나내어 추후 다른 패럴랙스 이펙트 효과로 안내합니다.
.parallax__nav는 고정된 위치(fixed)에서 parallax section으로 이동하는 내비게이션입니다.
.parallax__wrap은 가운데로 맞추고 약간의 여백을 주기 위해 width는 98%를 주었습니다.
각 parallax의 홀수 번째와 짝수 번째의 위치를 달리 하기 위해 nth-child(even)으로 right에 마이너스(-)를 주었습니다.
Javascript
window.addEventListener("scroll", () => {
// window.pageYOffset || window.scrollY || document.documentElement.scrollTop //모두 Y축 값을 구하는 메서드
let scrollTop = window.pageYOffset || window.scrollY || document.documentElement.scrollTop;
//스크롤 위치에 따라 메뉴 활성화
document.querySelectorAll(".parallax__item").forEach((item, index) => {
if(scrollTop > item.offsetTop - 2){
document.querySelectorAll(".parallax__nav li").forEach((li) => {
li.classList.remove("active");
});
document.querySelector(`.parallax__nav li:nth-child(${index+1})`).classList.add("active"); //Selector에서 nth 가능
}
});
document.querySelectorAll(".parallax__nav li a").forEach((li)=>{
li.addEventListener("click", (e) => {
e.preventDefault(); //메뉴를 클릭해도 이동하지 않음
document.querySelector(li.getAttribute("href")).scrollIntoView({
behavior: "smooth" //클릭하면 스크롤되면서 이동하는 애니메이션 효과
});
});
});
//info
document.querySelector(".parallax__info .scroll span").innerText = parseInt(scrollTop);
// document.querySelector(".info .offset1").innerText = document.getElementById("section1").offsetTop;
// document.querySelector(".info .offset2").innerText = document.getElementById("section2").offsetTop;
// document.querySelector(".info .offset3").innerText = document.getElementById("section3").offsetTop;
// document.querySelector(".info .offset4").innerText = document.getElementById("section4").offsetTop;
// document.querySelector(".info .offset5").innerText = document.getElementById("section5").offsetTop;
// document.querySelector(".info .offset6").innerText = document.getElementById("section6").offsetTop;
// document.querySelector(".info .offset7").innerText = document.getElementById("section7").offsetTop;
// document.querySelector(".info .offset8").innerText = document.getElementById("section8").offsetTop;
// document.querySelector(".info .offset9").innerText = document.getElementById("section9").offsetTop;
//for문
// for(let i = 1; i<=9; i++){
// document.querySelector(`.info .offset${i}`).innerText = document.getElementById(`section${i}`).offsetTop;
// }
// forEach문
// document.querySelectorAll(".parallax__wrap section").forEach((e, i)=>{
// document.querySelector(`.info .offset${i+1}`).innerText = e.offsetTop;
// });
// for in 문
// let parallaxSpan = document.querySelectorAll(".info span");
// for(let i in parallaxSpan){
// if(parallaxSpan.hasOwnProperty(i)){
// parallaxSpan[parseInt(i)].innerHTML = document.getElementById(`section${parseInt(i)+1}`).offsetTop;
// }
// };
// for of 문
let parallaxSpan = document.querySelectorAll(".info span");
// let sapnIndex = 0;
for(span of parallaxSpan.entries()){
// sapnIndex++;
span[1].innerHTML = document.getElementById(`section${span[0]+1}`).offsetTop;
}
});
자바스크립트에서 window 객체는 브라우저 창을 나타내는 전역객체입니다.
window 객체에 이벤트 리스너를 등록하여 브라우저에서 발생하는 다양한 이벤트에 동작을 수행할 수 있습니다.
브라우저의 scroll 위치에 따라 nav와 info를 활성화해야 하므로 window 객체에 이벤트 리스너를 등록합니다.
pageYOffset, scrollY 등은 브라우저의 Y축 위치값을 반환하는 속성입니다.
parallax__item에 forEach문을 이용하여 각 요소의 속성을 관리할 수 있습니다.
scroll의 위치가 item의 위치보다 커지는 순간, 즉 사용자가 해당 section을 보고 있다고 여겨지는 순간에 active를 지우고 해당 item에 active class를 추가합니다. (정확도를 위해 item 위치에서 -2를 합니다.)
preventDefault()는 자바스크립트의 이벤트 객체에서 제공하는 메서드 중 하나로, 이벤트의 기본 동작을 중지시킵니다.
nav를 클릭했을 때 이동하는 효과를 주기 위해 scrollIntroView() 메서드를 이용합니다.
scrollIntroView() 메서드는 특정 요소(li의 a 태그 중 href)를 스크롤해서 보이도록 하는 역할을 합니다.
각 요소의 위치값 할당하기!
info의 scroll class의 span에는 브라우저의 스크롤 위치값을 innerText로 입력합니다.
각각의 info offset을 선택하고, 다른 id로 입력한 parallax의 각 section 위치값을 추가할 수도 있습니다.
이보다 더욱 코드를 간결하고 편하게 관리하기 위해선 for, forEach, for in, for of 등을 활용하는 것이 좋습니다.
for문은 변수 i가 각 요소의 index 역할을 합니다.
forEach문은 첫 번째 인자가 요소를 뜻하고, 두 번째 인자가 index 역할을 합니다.
for in 문은 반복해서 객체나 배열의 값을 가져와 처리할 때 사용합니다.
하지만 선택자 메서드(querySelectorAll)는 일반적인 객체와 달리 NodeList 객체를 반환하므로 별도의 과정이 필요합니다.
NodeList는 배열과 비슷한 객체이지만 배열과 다른 메서드와 프로퍼티를 가지기 때문입니다.
따라서 hasOwnProperty() 메서드를 사용하여 NodeList 객체가 상속받은 프로퍼티를 건너뛰어야 합니다.
hasOwnProperty() 메서드는 객체가 특정 프로퍼티를 직접 갖고 있는지를 나타내는 불리언(true or false) 값을 반환하므로 if문에 적용하면 상속받은 프로퍼티를 걸러냅니다.
또한 for in으로 반환하는 NodeList 객체의 프로퍼티는 문자열 반환이므로 parseInt()로 정수로 만들어야 합니다.
for of 문은 배열의 요소 값을 반환합니다.
하지만 선택자 메서드는 NodeList 객체를 반환하므로 NodeList 객체에서 현재 요소의 인덱스를 알아내기 위해서 entries() 메서드를 이용해야 합니다.
entries() 메서드는 해당 객체의 index와 value의 쌍을 반환합니다.
따라서 [0]은 index을 뜻하고, [1]은 value인 요소를 뜻하기 때문에 위와 같은 코드를 작성해 요소의 위치 정보를 HTML 요소에 입력할 수 있습니다.