“ 지연되는 프로젝트에 인력을 더 투입하면 오히려 더 늦어진다. ”
- Frederick Philips Brooks
Mythical Man-Month 저자
포트폴리오 메인페이지 만들어보기(gsap)
💜 포트폴리오 메인페이지
폰트CSS
@font-face {
font-family: 'PPNeueWorld-CondensedRegular';
font-weight: normal;
font-style: normal;
src: url('fonts/PPNeueWorld-CondensedRegular.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'PPNeueWorld-ExtendedThin';
font-weight: normal;
font-style: normal;
src: url('fonts/PPNeueWorld-ExtendedThin.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'PPNeueWorld-Regular';
font-weight: normal;
font-style: normal;
src: url('fonts/PPNeueWorld-Regular.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'PPNeueWorld-SemiCondensedUltrabold';
font-weight: normal;
font-style: normal;
src: url('fonts/PPNeueWorld-SemiCondensedUltrabold.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'PPNeueWorld-SemiExtendedBlack';
font-weight: normal;
font-style: normal;
src: url('fonts/PPNeueWorld-SemiExtendedBlack.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'PPNeueWorld-SuperCondensedLight';
font-weight: normal;
font-style: normal;
src: url('fonts/PPNeueWorld-SuperCondensedLight.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'PPNeueWorld-Thin';
font-weight: normal;
font-style: normal;
src: url('fonts/PPNeueWorld-Thin.woff2') format('woff2');
font-display: swap;
}
디자인에서 폰트가 가장 중요하기 때문에
여러가지 사용할 폰트를 @font-face를 사용해 각각 불러와줬습니다.
font-family속성을 이용해 각각의 폰트에 이름을 지정해주고, 스타일을 설정해줬습니다.
모든 폰트 파일은 WOFF2형식으로 가져왔습니다.
*WOFF2는 Web Opne Font Format 2의 약어로
웹에서 사용되는 폰트 형식 중 하나인데
TTF, OTF 형식의 폰트 파일을 압축해 더 작은 파일크기를 제공하면서 웹에서 더 빠르게 로드될 수 있도록 하는 형식입니다.
HTML
<body>
<header id="header">
<h1>HHH PORTFOLIO</h1>
<nav>
<ul>
<li><a href="#">work</a></li>
<li><a href="#">about</a></li>
</ul>
</nav>
</header>
<!-- //header -->
<main id="main">
<div class="text__inner">
<div class="ti1">Let's <em>introduce</em></div>
<div class="ti2 split">frontend developer's</div>
<div class="ti3"><em>all</em> works <em>of</em> portfolio</div>
</div>
<div class="img__inner">
<img class="ii1" src="img/figure01.png" alt="이미지1">
<img class="ii2" src="img/figure02.png" alt="이미지2">
<img class="ii3" src="img/figure03.png" alt="이미지3">
</div>
<div id="webgl">
<iframe src="three.html" frameborder="0"></iframe>
</div>
</main>
<!-- //main -->
<footer id="footer">
<a href="https://mi-1119.tistory.com/" target="_blank">mi-1119.tistory.com</a>
</footer>
<!-- //footer -->
header, main, footer로 나누어줬습니다.
폰트를 각각 지정해줄거기 때문에
em태그로 나눠줬습니다.
iframe을 사용해 배경으로 들어갈 three.html을 로드해 사용해줬습니다.
✨
배경은 이 링크를 통해 가져왔습니다.
코드는 링크로 확인해주세요!
CSS
/* header */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100%;
height: 100vh;
background-color: #0b130d;
overflow: hidden;
}
#header {
position: fixed;
left: 0;
top: 0;
width: 100%;
display: flex;
align-items: flex-end;
justify-content: space-between;
padding: 10px 20px;
font-family: 'Abel';
z-index: 1000;
}
#header h1 {
font-weight: normal;
color: #D2E3C0;
font-size: 28px;
}
#header nav li {
list-style: none;
display: inline-block;
}
#header nav li a {
color: #D2E3C0;
text-transform: uppercase;
font-weight: normal;
padding: 10px;
font-size: 18px;
}
#footer {
position: fixed;
left: 50%;
bottom: 1vh;
transform: translateX(-50%);
z-index: 1000;
}
#footer a {
color: #fff;
font-family: 'Abel';
text-decoration: none;
}
#footer a:hover {
text-decoration: underline;
text-underline-position: under;
}
#main {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
position: relative;
}
.text__inner {
text-align: center;
color: #D2E3C0;
position: relative;
z-index: 3000;
}
.text__inner>div {
font-size: 8vw;
line-height: 1.2;
}
.text__inner>div.ti1 {
font-family: 'PPNeueWorld-SemiCondensedUltrabold';
}
.text__inner>div.ti1 em {
font-family: 'PPNeueWorld-Thin';
font-style: normal;
}
.text__inner>div.ti2 {
font-size: 80px;
font-family: 'PPNeueWorld-SemiExtendedBlack';
}
.text__inner>div.ti3 {
font-family: 'PPNeueWorld-SemiCondensedUltrabold';
}
.text__inner>div.ti3 em {
font-family: 'PPNeueWorld-ExtendedThin';
}
.img__inner>img {
position: absolute;
width: 10vw;
z-index: 2000;
}
.img__inner .ii1 {
left: 45%;
top: 30%;
transform: translateY(-200%);
}
.img__inner .ii2 {
left: 10%;
top: 55%;
transform: translateY(-180%);
}
.img__inner .ii3 {
left: 85%;
top: 55%;
transform: translateY(-180%);
}
#webgl {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100vh;
z-index: 1;
}
#webgl iframe {
width: 100%;
height: 100%;
}
나눠줬던 태그안에 폰트를 각각 지정해줬습니다.
body에 overflow : hidden을 설젖ㅇ해 페이지가 넘치는 경우 스크롤바가 표시되지 않도록 해줬습니다.
header, footer에 position : fixed를 사용해 페이지 위치에 고정시켜줬습니다.
gsap
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
<script>
//글씨 분리하기
document.querySelectorAll(".split").forEach(desc => {
let splitText = desc.innerText;
let splitWrap = splitText.split('').join("</span><span aria-hidden:'true'>");
splitWrap = "<span aria-hidden:'true'>" + splitWrap + "</span>";
desc.innerHTML = splitWrap;
desc.setAttribute("aria-label", splitText);
});
//메인 기본세팅
gsap.set(".text__inner .ti1", { opacity: 0, y: "10vh" });
gsap.set(".text__inner .ti2 span", { opacity: 0, x: 500, scale: 4, display: "inline-block", minWidth: "1.4vw" });
gsap.set(".text__inner .ti3", { opacity: 0, y: "-10vh" });
gsap.set(".img__inner .ii1", { opacity: 0, scale: 10, rotation: 360 });
gsap.set(".img__inner .ii2", { opacity: 0, scale: 10, rotation: 360 });
gsap.set(".img__inner .ii3", { opacity: 0, scale: 10, rotation: 360 });
gsap.set("#header", { opacity: 0, scale: 10 });
gsap.set("#footer", { bottom: -100 });
gsap.set("#webgl", { opacity: 0 });
//메인 애니메이션
setTimeout(() => {
let tl = gsap.timeline(); //delay필요없이 순차적으로 가능(콜백함수)
tl.to(".text__inner .ti2 span", { opacity: 1, x: 0, scale: 1, duration: 0.6, stagger: 0.1, ease: Power3.easeInOut })
tl.to(".text__inner .ti1", { opacity: 1, y: 0, duration: 0.5, ease: Circ.easeOut }, "hhh +=0.5") //"hhh"추가로 같이 실행되게할수있음
tl.to(".text__inner .ti3", { opacity: 1, y: 0, duration: 0.5, ease: Circ.easeOut }, "hhh +=0.5")
tl.to(".img__inner .ii1", { opacity: 1, scale: 1, duration: 0.6, rotation: 0 })
tl.to(".img__inner .ii2", { opacity: 1, scale: 1, duration: 0.6, rotation: 0 })
tl.to(".img__inner .ii3", { opacity: 1, scale: 1, duration: 0.6, rotation: 0 })
tl.to("#header", { opacity: 1, scale: 1, duration: 0.6 });
tl.to("#footer", { duration: 1, bottom: "1vw", ease: Power3.easeInOut }, "end");
tl.to("#webgl", { duration: 1, opacity: 1 });
}, 3000);
</script>
GSAP 라이브러리를 사용해 웹 페이지에서 애니메이션을 추가하는 코드입니다.
innerText를 사용해 선택한 요소의 텍스트를 가져온 다음, split()를 사용해 텍스트를 한글자씩 분리해줍니다.
그런 다음 join()을 사용해 분리된 글자들을 다시 하나의 문자열로 결합하면 </span><span aria-hidden:'true'>가 추가됩니다.
그런 다음 aplitWrap 문자열 앞뒤에 <span aria-hidden:'true'>추가해 span요소로 감싸고 innerHTML을 사용해 선택한 요소의 내용을 변경해 setAttribute()를 사용해 aria-label속성에 선택한 요소의 텍스트를 추가합니다.
gsap.set()을 사용해 초기 상태를 설정해주고, setTimeout()함수를 사용해 3초 후에 애니메이션을 시작합니다.
콜백 함수를 호출해 지연을 추가합니다.
이어서 gsap.timeline() 으로 타임라인 객체를 만들어 각 요소의 애니메이션을 순차적으로 실행할 수 있도록 합니다. tl.to()함수를 사용해 애니메이션을 추가해, 각 애니메이션 실행 시간(duration), easing함수(sase), delay시간(stagger)등을 설정해줍니다. tl.to()함수의 마지막 파라미터인 hhh += 0.5 는 hhh라는 라벨로 추가해 이전 애니메이션 종료 후 0.5초 뒤에 실행하게 합니다.
마지막으로 각 요소들의 애니메이션이 모두 끝난 후 setTimeout()함수에 설정한 1초 후에 #webgl 요소의 투명도를 1로 변경해 페이지에 표시합니다.