📌
사이트 명: Tamburins 반응형 (Redesign)
제작 기간: 22.09.16 ~ 22.09.20 (5일 소요)
사용 언어: html, scss, jQuery, java script
[Tamburins 반응형 WEB 제작 작업]
GSAP ScrollTrigger 라이브러리를 활용하여 다양한 스크롤 애니메이션 기능들을 구현한 반응형 웹 페이지입니다.
💡Learning point
- 메뉴 hover 애니메이션
- 메인 텍스트 효과
- 동영상 모달 창
- gsap 애니메이션
- 좌우 슬라이드 영역
먼저, closeSub()
이라는 함수에 hover 해제 시에 실행될 모션들을 담아두고, 해당 영역의 hover가 해제될 때 호출해서 전체 영역을 닫은 후에 해당 영역만 이벤트를 지정해서 실행시켰다.
function closeSub() {
$(".hover-wrap").removeClass("active");
$(".btn-search").removeClass("open");
$(".btn-packaging").removeClass("open");
$("body").removeClass("hide");
$(".group-sub").removeClass("active");
$(".header .border").removeClass("active");
$(".dimmed").css("display", "none");
}
상단 gnb 메뉴에는 products 항목에만 하위 영역이 있어서
해당 영역에만 active 클래스가 추가되도록 적용했다.
//gnb 메뉴 열고 닫기
$(".gnb-item a").mouseover(function (e) {
//전체 hover영역 닫기
closeSub();
//해당 영역 data값이 있는 경우에만 active 클래스 추가
target = $(this).data("hover");
$(target).addClass("active").siblings().removeClass("active");
utilMenuMotion.play();
//active 클래스 있을 때
if ($(target).hasClass("active")) {
$(".dimmed").css("display", "block");
$(".header .border").addClass("active");
$("body").addClass("hide");
//active 클래스 없을 때
} else {
$(".dimmed").css("display", "none");
$(".header .border").removeClass("active");
$("body").removeClass("hide");
}
});
//해당 영역에서 mouseleave 되면 전체 영역 닫기
$(".group-sub").mouseleave(function (e) {
closeSub();
$(".dimmed").css("display", "none");
utilMenuMotion.reverse();
});
좌측 유틸 영역 버튼 하버 시, 해당 하위 영역 슬라이드 다운 되도록 설정
$(".util-area button").mouseover(function (e) {
e.preventDefault();
//먼저 전체 영역 닫아주기
closeSub();
target = $(this).data("target");
$(target).addClass("active").siblings(".hover-wrap").removeClass("active");
$("body").addClass("hide");
utilMenuMotion.play();
//discover 영역에 mouseover 됐을 때
if (target == "#hover01") {
$(".btn-packaging").addClass("open");
$(".dimmed").css("display", "block");
//search 영역에 mouseover 됐을 때
} else if (target == "#hover02") {
$(".btn-search").addClass("open");
$(".dimmed").css("display", "block");
//둘 다 아닐 경우엔 둘 다 해제
} else {
$(".btn-packaging").removeClass("open");
$(".btn-search").removeClass("open");
}
});
스크롤 시 변경되는 header영역 디자인을 메뉴 hover시에는 해제 시키기
$(".util-area button").mouseover(function (e) {
e.preventDefault();
target = $(this).data("target");
//해당 버튼 영역이 open 상태일 때,
if ($(this).hasClass("open")) {
//header 배경 제거
$(".header-inner").removeClass("background");
//아닌 경우엔 배경 추가
} else {
$(".header-inner").addClass("background");
}
});
[css]
.visual-wrap {
height: 100vh;
width: 100%;
border-radius: 0 0 40px 40px;
//하얀색 글씨 영역
.front-cont {
position: absolute;
z-index: 10;
top: 0;
left: 0;
margin: 0 auto;
width: 100%;
height: calc(100vh - 20px);
border-radius: 0 0 40px 40px;
overflow: hidden;
..이하 생략
}
//검정색 글씨 영역
.back-cont {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: calc(100vh - 20px);
overflow: hidden;
border-radius: 0 0 40px 40px;
//스크롤을 내렸을 때 active되면서 검정색 글씨가 보이게
&.active {
position: sticky;
}
}
.title {
position: absolute;
z-index: 20;
top: 0;
left: 0;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
.main-txt {
position: absolute;
top: 25%;
left: 50%;
transform: translateX(-50%);
font-size: 128px;
width: 1040px;
padding: 40px;
line-height: 1;
color: #fff;
font-weight: 300;
//글자가 누운 상태에서 등장하도록 원근값을 설정
perspective: 400px;
text-align: center;
font-family: "DMserif";
span {
display: block;
}
..이하 생략
}
}
}
[js]
//메인 비주얼 영역
gsap.to(".visual-wrap .front-cont", {
scrollTrigger: {
trigger: ".visual-wrap",
start: "top top",
end: "bottom top",
// markers: true,
scrub: 1,
pin: true
},
//높이값이 0이 되도록
height: 0
});
//3d 텍스트 효과
gsap.set(".main-txt span", {
yPercent: 110,
transformStyle: "preserve-3d",
rotationX: 90,
//transform을 적용할 x,y,z축 값 설정
transformOrigin: "0% 80% -100%"
});
gsap.to(".main-txt span", 1, {
delay: 0.2,
yPercent: 0,
rotationX: 0
});
[html]
//재생 버튼에 영상 data값 작성, 모달창은 스크립트에서 동작하도록 마크업 생략
<div class="group-video">
<div class="txt">
<strong class="title" data-opacity>Our<br>commitment</strong>
</div>
<div class="btn-wrap"><a href="#" class="btn-play" data-video="XDhsibRT9rU"><span class="blind">영상 재생하기</span></a></div>
</div>
//video 모달 팝업
$(".group-video .btn-play").click(function (e) {
e.preventDefault();
//해당 영상 data 주소 값 받아오기
url = $(this).data("video");
//추가될 html 영역
html = `<div class="video-modal">
<div class="dimmed1"></div>
<a href="" class="btn-close" role="button"><i class="bi bi-x"><span class="blind">닫기</span></i></a>
<div class="video-wrapper">
<iframe width="560" height="315" src="https://www.youtube.com/embed/${url}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
</div>`;
//해당 영역에 작성된 구조 추가
$(".sc-expertise").append(html);
});
//닫기 버튼과 dimmed영역 클릭시 모달 창 닫히도록
$(document).on("click", ".btn-close, .dimmed1", function (e) {
e.preventDefault();
$(".video-modal").remove();
});
텍스트는 스크롤 시 fade up되고, line은 width값이 조절되도록 모션 적용
//sc-approach 영역
$("[data-fade]").each(function (i, l) {
gsap.from(".sc-approach .txt-wrap", {
scrollTrigger: {
trigger: l,
start: "top 50%",
end: "bottom 70%",
// markers: true,
scrub: 1
},
opacity: 0,
yPercent: 20
});
});
gsap.from("[data-line]", 1, {
scrollTrigger: {
trigger: ".sc-approach .txt-wrap",
start: "top 50%",
end: "bottom 60%",
scrub: 1
// markers: true,
},
width: 0
});
스크롤 시, 두 개의 텍스트가 좌우 양 끝에서 각각 등장하도록 모션 적용
[html]
data값으로 이동시킬 x축의 값을 담는다
<h2 class="txt-scroll">
<span class="txt left" data-x="-100">Discover</span>
<span class="txt right" data-x="80">our brands</span>
</h2>
[js]
//텍스트 스크롤 영역
const slideTxt = document.querySelectorAll("[data-x]");
slideTxt.forEach((el) => {
//해당 텍스트의 x값을 변수에 담는다
xVal = el.dataset.x;
gsap.from(el, {
scrollTrigger: {
trigger: ".sc-approach .link-more",
start: "bottom 100%",
end: "+=200%",
// markers: true,
scrub: 1
},
//각 x축 값만큼 이동하도록 설정
xPercent: xVal
});
});
만들면서 제일 어려웠던.. 슬라이드 영역..ㅠ 오른쪽 영역에 position: sticky
적용하면 될 것 같았는데 도저히 모르겠어서 scrolltrigger로 제작했다.
[html]
<ul class="content-list">
//각 영역 data 값으로 적용시킬 배경 색상 설정
<li class="content-item bg01" data-color="#7ab5cc" id="nav01">
..이하 생략
</li>
<li class="content-item bg02" data-color="#97a880" id="nav02">
..이하 생략
</li>
<li class="content-item bg03" data-color="#636363" id="nav03">
..이하 생략
</li>
<li class="content-item bg04" data-color="#e1cd5d" id="nav04">
..이하 생략
</li>
</ul>
[css]
//각 이미지가 높이 꽉 찬 상태로 스크롤 되도록 높이값 설정
.content-slide {
position: relative;
width: 100%;
height: 400vh;
}
[js]
//좌우 슬라이드
$(".content-left .content-item").each(function (i, el) {
target = $(".content-right .content-item").eq(i);
//해당 영역 순서에 따라 y축 스크롤 되도록 변수 값 저장
yVal = i == 3 ? 0 : -100;
ScrollTrigger.create({
trigger: el,
start: "0% 30%",
end: "bottom top",
//애니메이션이 진행되는 동안 함께 동작하도록 onUpdate 콜백 함수 적용
onUpdate: function (self) {
//data 값에 담아둔 색상 값을 호출해서 적용
gsap.to(".sc-brands .content-left", { background: el.dataset.color });
}
});
gsap.to(target, 1, {
scrollTrigger: {
trigger: el,
start: "0% 0%",
end: "bottom top",
// markers: true,
scrub: 0
},
ease: "none",
yPercent: yVal
});
});
[html]
<nav id="navbar">
<ul class="nav-list">
<li class="nav-item"><a id="navigation" href="#nav01" data-target="nav01" class="active">CANDLE</a></li>
<li class="nav-item"><a id="navigation" href="#nav02" data-target="nav02">PERFUME</a></li>
<li class="nav-item"><a id="navigation" href="#nav03" data-target="nav03">BODY</a></li>
<li class="nav-item"><a id="navigation" href="#nav04" data-target="nav04">HAND</a></li>
</ul>
</nav>
[js]
// 네비게이션
$(".content-right .nav-item a").click(function (e) {
e.preventDefault();
//클릭된 target의 href값 변수에 저장
target = $(this).attr("href");
//클릭된 a에 active 클래스 주기
$(this).addClass("active");
//해당되지 않은 영역에는 active 클래스 제거
$(".nav-item a").removeClass("active");
//클릭된 href값 영역으로 이동
gsap.to(window, { duration: 0.5, scrollTo: target });
});
//스크롤 스파이 제작
var link = $("#navbar a");
$(window).on("scroll", function () {
findPosition();
});
function findPosition() {
$(".content-left .content-item").each(function () {
//해당 스크롤 영역 도달하면
if ($(this).offset().top - $(window).scrollTop() < 10) {
//전체 요소 active 해제 후,
link.removeClass("active");
//해당 nav의 target값과 왼쪽 li영역 id값이 일치하는 nav에 active 클래스 부여
$("#navbar")
.find('[data-target="' + $(this).attr("id") + '"]')
.addClass("active");
}
});
}
findPosition();