QUOTE THE DAY
“ 당신이 6개월 이상 한 번도 보지 않은 코드는 다른 사람이 다시 만드는 게 훨씬 더 나을 수 있다. ”
-
이글슨 (Eagleson)
반응형
패럴랙스 이펙트란?
패럴랙스 이펙트는 사용자가 스크롤을 내리거나 올릴 때, 배경과 함께 웹 페이지의 요소들이 서로 다른 속도로 움직이는 것을 말합니다. 이는 일반적으로 CSS와 JavaScript를 사용하여 구현되며, 웹 페이지를 더욱 동적이고 인터랙티브하게 만들어줍니다.
웹사이트 패럴랙스 이펙트를 사용하면 사용자가 웹 페이지를 스크롤 할 때, 배경 이미지와 함께 다양한 요소들이 움직이는 것처럼 보입니다. 예를 들어, 웹 페이지에 일러스트나 이미지를 배경으로 사용하면 이를 이용하여 사용자의 눈길을 이끌거나 페이지의 구조를 강조할 수 있습니다. 또는 텍스트나 버튼과 같은 요소들도 패럴랙스 이펙트를 이용하여 동적인 효과를 줄 수 있습니다.
패럴랙스 이펙트는 웹 디자인의 트렌드 중 하나로, 사용자 경험을 개선하고 웹 페이지를 더욱 흥미롭고 동적으로 만들어줍니다. 하지만, 지나친 사용은 사용자 경험을 해치고 성능 저하를 일으킬 수 있으므로 적절한 사용이 필요합니다.
패럴랙스 이펙트 사이트 코드
<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 split style1">행동이 모든 것을 결정한다. 운명은 행동에서 비롯된다.</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 split style2">작은 성공이 큰 성공으로 이어진다.</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 split style3">인생은 결코 공평하지 않다. 그러나 그것을 이길 수 있다.</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 split style4">성취하지 못한 것은 아직 해보지 않은 것뿐입니다.</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 split style5">가장 높은 산은 한 발짝부터 시작된다.</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 split style6">목표를 향한 첫 걸음은 용기이다.</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 split style7">우리의 인생은 우리의 생각과 믿음이 결정한다.</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 split style8">언제나 새로운 것을 배울 수 있고, 새로운 것을 경험할 수 있다.</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 split">한계는 우리가 만든 것이며, 우리가 이겨낼 수 있다.</p>
</section>
<!-- //section9 -->
</div>
</main>
<!-- //main -->
//모든 텍스트 분리
let text = document.querySelectorAll(".split").forEach(text => {
let splitText = text.innerText;
let splitWrap = splitText.split('').join("</span><span aria-hidden='true'>");
text.innerHTML = splitWrap = "<span aria-hidden='true'>" + splitWrap + "</span>";
text.setAttribute("aria-label", splitText);
});
- document.querySelectorAll(".split")는 HTML 문서 내에서 클래스가 "split"으로 지정된 모든 요소를 선택합니다. 선택된 요소들은 NodeList 형태로 반환됩니다.
- forEach 메서드를 사용하여 NodeList의 각 요소에 대해 반복합니다. 각 요소를 text 변수에 할당합니다.
- innerText 속성은 해당 요소의 텍스트 내용을 가져옵니다. 이 값을 splitText 변수에 할당합니다.
- splitText.split('').join("</span><span aria-hidden='true'>")는 splitText 문자열을 각 문자마다 분리하고, 분리된 문자들을 </span><span aria-hidden='true'>로 연결하여 새로운 문자열을 생성합니다. 이 값은 splitWrap 변수에 할당됩니다.
- text.innerHTML = splitWrap은 해당 요소의 내부 HTML을 splitWrap 값으로 설정합니다. 이로써 각 문자가 <span> 요소로 감싸지고, aria-hidden='true' 속성이 추가되게 됩니다.
- text.setAttribute("aria-label", splitText)은 해당 요소에 "aria-label" 속성을 추가하고, 그 값으로 splitText를 설정합니다. 이는 스크린 리더기와 같은 보조 기술에서 텍스트를 읽을 때 사용됩니다.
JS
//스크롤 이펙트
function scroll(){
let scrollTop = window.pageYOffset || window.scrollY;
//01. CSS 클래스 추가 (노가다)
document.querySelectorAll(".parallax__item").forEach(item => {
if(scrollTop > item.offsetTop){
item.querySelector(".split").classList.add("show");
}
});
requestAnimationFrame(scroll);
}
scroll();
- let scrollTop = window.pageYOffset || window.scrollY;는 현재 스크롤 위치를 가져옵니다. window.pageYOffset 또는 window.scrollY를 사용하여 호환성을 고려합니다.
- document.querySelectorAll(".parallax__item").forEach(item => { ... })는 클래스가 "parallax__item"으로 지정된 모든 요소를 선택합니다. 선택된 각 요소에 대해 반복하면서 다음 작업을 수행합니다.
- if(scrollTop > item.offsetTop)은 현재 스크롤 위치가 요소의 상단 위치보다 큰지 확인합니다. 즉, 스크롤이 해당 요소를 지나갔는지를 판단합니다.
- item.querySelector(".split")은 현재 요소에서 클래스가 "split"으로 지정된 하위 요소를 선택합니다.
- classList.add("show")는 선택된 요소의 클래스 리스트에 "show" 클래스를 추가합니다. 이로써 "show" 클래스가 있는 경우 해당 요소에 대한 특정 스타일이 적용되어 시각적인 효과가 나타납니다. 이 부분은 해당 요소가 스크롤을 통해 보여지는 시점에서 클래스를 추가하는 것으로 추측됩니다.
- requestAnimationFrame(scroll)은 다음 애니메이션 프레임을 요청하여 scroll 함수를 계속해서 실행하도록 합니다. 이를 통해 스크롤 이벤트가 발생할 때마다 부드러운 애니메이션 효과를 제공합니다.
- scroll() 함수를 호출하여 스크롤 이벤트를 시작합니다. 이를 통해 페이지가 로드될 때부터 스크롤 이펙트가 작동합니다.
CSS
.split span {
display: inline-block;
min-width: 1vw;
opacity: 0;
transform: translateY(100px);
transition: all 0.8s cubic-bezier(0.075, 0.82, 0.165, 1);
}
.split.show span {
opacity: 1;
transform: translateY(0);
}
.split span:nth-child(1) {transition-delay: 100ms;}
.split span:nth-child(2) {transition-delay: 150ms;}
.split span:nth-child(3) {transition-delay: 200ms;}
.split span:nth-child(4) {transition-delay: 250ms;}
.split span:nth-child(5) {transition-delay: 300ms;}
.split span:nth-child(6) {transition-delay: 350ms;}
.split span:nth-child(7) {transition-delay: 400ms;}
.split span:nth-child(8) {transition-delay: 450ms;}
.split span:nth-child(9) {transition-delay: 500ms;}
.split span:nth-child(10) {transition-delay: 550ms;}
.split span:nth-child(11) {transition-delay: 600ms;}
.split span:nth-child(12) {transition-delay: 650ms;}
.split span:nth-child(13) {transition-delay: 700ms;}
.split span:nth-child(14) {transition-delay: 750ms;}
.split span:nth-child(15) {transition-delay: 800ms;}
.split span:nth-child(16) {transition-delay: 850ms;}
.split span:nth-child(17) {transition-delay: 900ms;}
.split span:nth-child(18) {transition-delay: 950ms;}
.split span:nth-child(19) {transition-delay: 1000ms;}
.split span:nth-child(20) {transition-delay: 1050ms;}
.split span:nth-child(21) {transition-delay: 1100ms;}
.split span:nth-child(22) {transition-delay: 1150ms;}
.split span:nth-child(23) {transition-delay: 1200ms;}
.split span:nth-child(24) {transition-delay: 1250ms;}
.split span:nth-child(25) {transition-delay: 1300ms;}
.split span:nth-child(26) {transition-delay: 1350ms;}
.split span:nth-child(27) {transition-delay: 1400ms;}
.split span:nth-child(28) {transition-delay: 1450ms;}
.split span:nth-child(29) {transition-delay: 1500ms;}
.split span:nth-child(30) {transition-delay: 1550ms;}
.split span:nth-child(31) {transition-delay: 1600ms;}
.split span:nth-child(32) {transition-delay: 1650ms;}
.split span:nth-child(33) {transition-delay: 1700ms;}
.split span:nth-child(34) {transition-delay: 1750ms;}
.split span:nth-child(35) {transition-delay: 1800ms;}
.split span:nth-child(36) {transition-delay: 1850ms;}
- .split span은 클래스가 "split"이고 하위 요소로 <span>인 모든 요소를 선택합니다. 이는 텍스트를 분할하여 각 문자를 개별적으로 처리하는 데 사용됩니다.
- .split.show span은 .split 클래스와 .show 클래스가 함께 지정된 <span> 요소를 선택합니다. 이는 특정 순간에 해당 요소를 보여주는 데 사용됩니다.
- .split span:nth-child(n) {transition-delay: Xms;}은 n번째 자식인 <span> 요소에 대한 전환 지연 시간을 설정합니다. n은 1부터 36까지의 값을 가질 수 있습니다. Xms는 전환 지연 시간을 밀리초 단위로 지정합니다. 예를 들어, .split span:nth-child(1) {transition-delay: 100ms;}는 첫 번째 <span> 요소의 전환 지연 시간을 100밀리초로 지정합니다. 이를 통해 각 문자가 일정한 시간 간격으로 순차적으로 나타납니다.
JS
//스크롤 이펙트
function scroll(){
let scrollTop = window.pageYOffset || window.scrollY;
// 02. span에 show 추가
document.querySelectorAll(".parallax__item").forEach(item => {
if(scrollTop >= item.offsetTop){
item.querySelectorAll(".split span").forEach((span, index) => {
setTimeout(() => {
span.classList.add("show");
}, 50 * index);
});
}
});
requestAnimationFrame(scroll);
}
scroll();
- let scrollTop = window.pageYOffset || window.scrollY;는 현재 스크롤 위치를 가져옵니다. window.pageYOffset 또는 window.scrollY를 사용하여 호환성을 고려합니다.
- document.querySelectorAll(".parallax__item").forEach(item => { ... })는 클래스가 "parallax__item"으로 지정된 모든 요소를 선택합니다. 선택된 각 요소에 대해 반복하면서 다음 작업을 수행합니다.
- if(scrollTop >= item.offsetTop)은 현재 스크롤 위치가 요소의 상단 위치보다 크거나 같은지 확인합니다. 즉, 스크롤이 해당 요소를 지나거나 도달했는지를 판단합니다.
- item.querySelectorAll(".split span").forEach((span, index) => { ... })는 현재 요소에서 클래스가 "split"으로 지정된 하위 요소 중 모든 <span> 요소를 선택합니다. 선택된 각 <span> 요소와 해당 인덱스에 대해 반복하면서 다음 작업을 수행합니다.
- setTimeout(() => { span.classList.add("show"); }, 50 * index);은 일정 시간 간격으로 특정 <span> 요소에 "show" 클래스를 순차적으로 추가합니다. setTimeout 함수를 사용하여 인덱스에 따라 지연 시간을 조정하여 순차적인 효과를 만듭니다. 50밀리초마다 한 번씩 show 클래스가 추가되므로, 텍스트가 한 글자씩 보여지는 효과를 얻을 수 있습니다.
- requestAnimationFrame(scroll)은 다음 애니메이션 프레임을 요청하여 scroll 함수를 계속해서 실행하도록 합니다. 이를 통해 스크롤 이벤트가 발생할 때마다 부드러운 애니메이션 효과를 제공합니다.
- scroll() 함수를 호출하여 스크롤 이벤트를 시작합니다. 이를 통해 페이지가 로드될 때부터 스크롤 이펙트가 작동합니다.
CSS
/* 기본효과 */
.style1.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
transition: all 0.3s ease-in-out;
}
.style1.split span.show {
opacity: 1;
}
/* 효과 2밑에서 올라오기 */
.style2.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
transform: translateY(100px);
transition: all 0.3s ease-in-out;
}
.style2.split span.show {
opacity: 1;
transform: translateY(0);
}
/* 효과3 */
.style3.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
transform: translateY(100px) translateX(-100px) rotate(360deg);
transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
}
.style3.split span.show {
opacity: 1;
transform: translateY(0) translateX(0) rotate(0);
}
/* 효과4 */
.style4.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
}
.style4.split span.show {
opacity: 1;
animation: jackInTheBox 0.5s 1;
}
/* 효과5 */
.style5.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
}
.style5.split span.show {
opacity: 1;
animation: hinge 0.5s 1;
}
/* 효과6 */
.style6.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
}
.style6.split span.show {
opacity: 1;
animation: wobble 0.5s 1;
}
/* 효과7 */
.style7.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
}
.style7.split span.show {
opacity: 1;
animation: slideInLeft 0.5s 1;
}
/* 효과8 */
.style8.split span {
opacity: 0;
display: inline-block;
min-width: 1vw;
}
.style8.split span.show {
opacity: 1;
animation: bounceIn 0.5s 1;
}
@keyframes jackInTheBox {
0% {
opacity: 0;
transform: scale(.1) rotate(30deg);
transform-origin: center bottom
}
50% {
transform: rotate(-10deg)
}
70% {
transform: rotate(3deg)
}
to {
opacity: 1;
transform: scale(1)
}
}
@keyframes hinge {
0% {
transform-origin: top left;
animation-timing-function: ease-in-out
}
20%,
60% {
transform: rotate(80deg);
transform-origin: top left;
animation-timing-function: ease-in-out
}
40%,
80% {
transform: rotate(60deg);
transform-origin: top left;
animation-timing-function: ease-in-out;
opacity: 1
}
to {
transform: translate3d(0, 700px, 0);
opacity: 0
}
}
@keyframes wobble {
0% {
-webkit-transform: translateZ(0);
transform: translateZ(0)
}
15% {
-webkit-transform: translate3d(-25%, 0, 0) rotate(-5deg);
transform: translate3d(-25%, 0, 0) rotate(-5deg)
}
30% {
-webkit-transform: translate3d(20%, 0, 0) rotate(3deg);
transform: translate3d(20%, 0, 0) rotate(3deg)
}
45% {
-webkit-transform: translate3d(-15%, 0, 0) rotate(-3deg);
transform: translate3d(-15%, 0, 0) rotate(-3deg)
}
60% {
-webkit-transform: translate3d(10%, 0, 0) rotate(2deg);
transform: translate3d(10%, 0, 0) rotate(2deg)
}
75% {
-webkit-transform: translate3d(-5%, 0, 0) rotate(-1deg);
transform: translate3d(-5%, 0, 0) rotate(-1deg)
}
to {
-webkit-transform: translateZ(0);
transform: translateZ(0)
}
}
@keyframes slideInLeft {
0% {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
visibility: visible
}
to {
-webkit-transform: translateZ(0);
transform: translateZ(0)
}
}
@keyframes bounceIn {
0%,
20%,
40%,
60%,
80%,
to {
-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
animation-timing-function: cubic-bezier(.215, .61, .355, 1)
}
0% {
opacity: 0;
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3)
}
20% {
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1)
}
40% {
-webkit-transform: scale3d(.9, .9, .9);
transform: scale3d(.9, .9, .9)
}
60% {
opacity: 1;
-webkit-transform: scale3d(1.03, 1.03, 1.03);
transform: scale3d(1.03, 1.03, 1.03)
}
80% {
-webkit-transform: scale3d(.97, .97, .97);
transform: scale3d(.97, .97, .97)
}
to {
opacity: 1;
-webkit-transform: scaleX(1);
transform: scaleX(1)
}
}
- 각 효과는 .style1부터 .style8까지의 클래스를 가지고 있으며, .split span 선택자를 사용하여 <span> 요소에 적용됩니다. 각 효과는 .show 클래스가 함께 적용될 때 활성화됩니다.
- 애니메이션 효과는 @keyframes 규칙을 사용하여 정의됩니다. 각 효과의 애니메이션 키프레임은 효과의 시작 상태부터 끝 상태까지의 변화를 정의합니다.
- 예를 들어, @keyframes jackInTheBox는 Jack in the Box 효과에 대한 애니메이션 키프레임을 정의합니다. 이 효과는 요소가 작아지면서 회전하여 나타나는 효과를 제공합니다.
- 각 효과는 해당 효과의 스타일과 애니메이션 효과를 결합하여 텍스트 분할 효과에 다양한 동적인 효과를 부여합니다.
JS
/스크롤 이펙트
function scroll(){
let scrollTop = window.pageYOffset || window.scrollY;
// //03. gsap
const items = document.querySelectorAll(".parallax__item");
items.forEach((item, index) => {
const spans = item.querySelectorAll(".split span");
if(scrollTop >= item.offsetTop){
gsap.to(spans, {
duration: 0.1,
opacity: 1,
x: 0,
y: 0,
z: 0,
scale: 1,
rotation: 0,
stagger: 0.05
});
}
});
requestAnimationFrame(scroll);
}
scroll();
- let scrollTop = window.pageYOffset || window.scrollY;는 현재 스크롤 위치를 가져옵니다. window.pageYOffset 또는 window.scrollY를 사용하여 호환성을 고려합니다.
- const items = document.querySelectorAll(".parallax__item");은 클래스가 "parallax__item"으로 지정된 모든 요소를 선택합니다. 선택된 요소들을 items 변수에 할당합니다.
- items.forEach((item, index) => { ... })은 items 요소들에 대해 반복하면서 다음 작업을 수행합니다. 각 요소와 해당 인덱스에 대해 콜백 함수를 실행합니다.
- const spans = item.querySelectorAll(".split span");은 현재 요소에서 클래스가 "split"으로 지정된 하위 요소 중 모든 <span> 요소를 선택합니다. 선택된 요소들을 spans 변수에 할당합니다.
- if(scrollTop >= item.offsetTop)은 현재 스크롤 위치가 요소의 상단 위치보다 크거나 같은지 확인합니다. 즉, 스크롤이 해당 요소를 지나거나 도달했는지를 판단합니다.
- gsap.to(spans, { ... })은 GSAP 라이브러리의 to 메서드를 사용하여 spans 요소들을 애니메이션화합니다. 제공된 속성들을 사용하여 애니메이션 효과를 지정합니다. 예를 들어, duration은 애니메이션의 지속 시간을, opacity는 투명도를, x, y, z는 위치를, scale은 크기를, rotation은 회전을 나타냅니다. stagger 속성을 사용하여 각 요소의 애니메이션 지연 시간을 조정하여 순차적인 효과를 만듭니다.
- requestAnimationFrame(scroll)은 다음 애니메이션 프레임을 요청하여 scroll 함수를 계속해서 실행하도록 합니다. 이를 통해 스크롤 이벤트가 발생할 때마다 부드러운 애니메이션 효과를 제공합니다.
- 마지막으로, scroll() 함수를 호출하여 스크롤 이벤트를 시작합니다. 이를 통해 페이지가 로드될 때부터 스크롤 이펙트가 작동합니다.
CSS
/* gsap */
.split {
transform-style: preserve-3d;
}
.split span {
display: inline-block;
min-width: 1vw;
opacity: 0;
transform: translate3d(100px, 0px, 30px) scale(2) rotate(200deg);
}
- .split 클래스는 3D 변환을 유지하는 transform-style: preserve-3d; 속성을 가지고 있습니다.
- .split span 선택자는 <span> 요소에 적용되며, 인라인 블록 요소로 표시되고 최소 너비가 1vw로 설정됩니다. 초기에는 투명도가 0이며, 3D 변환을 사용하여 X축으로 100px, Y축으로 0px, Z축으로 30px 이동하고 크기를 2배로 확대한 후 200도 회전합니다.
이 스타일 규칙은 GSAP를 사용하여 텍스트 분할 요소에 동적인 애니메이션을 적용할 때 사용될 수 있습니다. GSAP는 풍부한 애니메이션 기능을 제공하며, 이러한 스타일 규칙과 함께 사용하면 웹 요소에 매끄럽고 인상적인 애니메이션 효과를 부여할 수 있습니다.
반응형