대대적인 리뉴얼을 통한 자기소개 웹페이지 완성 🙌
박스 그룹화로 div 묶는 법... CSS 변수 설정 및 속성 등 조금 더 공부해보니 초기에 기획했던 AS-IS가 말도안되게 엉망이었다는 것을 깨닫고 전체적인 리뉴얼 결심!!!
HTML 마크업, CSS 설정까지 전반적으로 수정하고 자바스크립트로 기능을 추가하였다.
제일 먼저 보게 될 홈페이지 인트로 부분에서 정적인 것보다는 동적인 애니메이션으로 포인트를 주면 좋을 것 같다고 생각하여 영상을 넣을지 방법을 고민하다가 타이핑효과를 구현하였다.
👇 html
<div class="home__title">
<h2 class="home__introtext"></h2>
</div>
👇 css
.home__introtext::after {
content: "|";
animation: blink 320ms step-end infinite;
}
@keyframes blink {
50% {
border-color: var(--color-white);
opacity: 0;
}
}
animation
: .home__introtext뒤에 해당 애니메이션을 넣어준다.
-> 함수명 / 반복시간 / timingfunction / 반복되는count@keyframes
: 애니메이션을 재생할 프레임의 스타일을 정의한다.
-> content의 색은 흰색으로 지정하고 실행 시 50% 구간에서 투명하게 해 깜빡거리는 것과 같은 효과를 줄 수 있다.
참고 : MDN(animation)
👇 자바스크립트
/* intro 타이핑효과 */
const introText = document.querySelector(".home__introtext");
const content = "안녕하세요.\n저는 구수정입니다 :)";
let index = 0;
let putWord = "";
const typingInterval = setInterval(() => {
putWord += content[index++];
introText.innerText = putWord;
// introText.innerText += content[index++];
// 위의 코드처럼 진행 시 띄어쓰기가 나올 경우 ex) "저는 "까지 입력된 innerText를 가져올 시 띄어쓰기가 trim됨
}, 320);
setTimeout(() => {
clearInterval(typingInterval);
}, 320 * content.length + 30);
textContent / innerText / innerHTML
textContent
는<script>
,<style>
요소를 포함한 모든 요소의 콘텐츠를 가져온다.innerText
는 렌더링이 되어 "사람이 읽을 수 있는" 요소만 처리한다.innerHTML
은 DOM트리를 수정하는 것으로 XSS 공격에 취약하므로 가급적 사용하지 않는것이 좋다.
setInterval()
: 한글자 입력하는데 설정한 시간인 0.32초마다 함수를 실행시킨다. 따라서 한글자씩 .home__introtext에 들어가진다.setTimeout()
: 모든 문장을 입력하는데 설정한 시간이 지나면 함수를 호출한다. -> 사파리에서 약간 시간지연 있어 +30 적용clearInterval()
: 해당 함수의 인터벌을 중단시킨다. 함수명 입력 시 '()'를 입력한다면 해당 함수의 반환값을 가져오는 것이므로 함수명만 적어준다.
👇 html
<ul class="navbar__menu">
<li class="navbar__menu__item selected" data-link="#home">Home</li>
<li class="navbar__menu__item" data-link="#about">About</li>
<li class="navbar__menu__item" data-link="#skill">Skill</li>
<li class="navbar__menu__item" data-link="#work">Portfolio</li>
<li class="navbar__menu__item" data-link="#footer">contact</li>
</ul>
👇 css
.navbar__menu {
display: flex;
flex-flow: row;
justify-content: space-evenly;
}
.navbar__menu__item {
padding: 10px;
margin: 0 10px;
cursor: pointer;
color: var(--color-white);
}
.navbar__menu__item:hover {
color: var(--color-blue);
border-radius: 15px;
background-color: var(--color-white);
}
hover
: 마우스 위에 커서를 올렸을 때 요소를 선택한다.
->.navbar__menu__item
요소 위에 마우스가 올라가면 이렇게 스타일링하겠다.
👇 자바스크립트
const navbarMenu = document.querySelector(".navbar__menu");
navbarMenu.addEventListener("click", (event) => {
// event.target : <li class="navbar__menu__item active">Home</li>
// event.target.dataset : DOMStringMap {link: '#about'}
const link = event.target.dataset.link;
if (link == null) return;
scrollinto(link);
});
/* 해당 목적지까지 스크롤 이동 */
function scrollinto(selector) {
const destination = document.querySelector(selector);
destination.scrollIntoView({ behavior: "smooth" });
}
addEventListener()
: 클릭 시 이벤트를 등록한다.event.target.dataset.link
: 클릭한 navbar__menu 클래스를 가진 요소의 data객체의 키 link의 value를 가져온다.scrollinto()
: 해당 value를 id값으로 가지고 있는 곳으로 스크롤이 이동한다. 옵션으로 behavior:"smooth"를 지정하여 스크롤이 부드럽게 이동하게끔 해주었다.
👇 자바스크립트
const intro = document.querySelector(".home__container");
const introHeight = intro.offsetHeight;
document.addEventListener("scroll", () => {
intro.style.opacity = 1 - window.scrollY / introHeight;
});
offsetHeight()
: 우리가 보고 있는 부분의 높이값을 가져오는 함수- scroll이벤트가 일어났을 때 scroll한만큼 Y값을 가져와서 opacity값을 지정한다.
👇 css
.top-btn {
position: fixed;
right: 30px;
bottom: 30px;
font-size: 50px;
color: var(--color-white);
border: none;
background-color: transparent;
opacity: 0;
/* opacity로 버튼을 가려놓은 상태라 해당 위치에 마우스를 올리면 포인터가 생기기 때문에
지워주는 스타일링 */
pointer-events: none;
transition: all 300ms ease-in;
}
.top-btn.visible {
opacity: 1;
pointer-events: auto;
cursor: pointer;
}
👇 자바스크립트
const topBtn = document.querySelector(".top-btn");
document.addEventListener("scroll", () => {
if (window.scrollY > introHeight / 2) {
topBtn.classList.add("visible");
} else {
topBtn.classList.remove("visible");
}
});
topBtn.addEventListener("click", () => {
scrollinto("#home");
});
/* 해당 목적지까지 스크롤 이동 */
function scrollinto(selector) {
const destination = document.querySelector(selector);
destination.scrollIntoView({ behavior: "smooth" });
}
- 스크롤이 첫화면의 반이상을 지났을 때
.top-btn
의 요소에 visible이라는 클래스를 추가하여 top버튼을 생성한다.- 해당 버튼을 클릭했을 때
#home
id를 가지고 있는 요소, 즉 첫화면으로 스크롤 이동시킨다.
👇 html
<div class="work__categories">
<button class="category__btn selected" data-filter="*">
all
<span class="category__count">3</span>
</button>
<button class="category__btn" data-filter="front">
front
<span class="category__count">2</span>
</button>
<button class="category__btn" data-filter="back">
back
<span class="category__count">1</span>
</button>
</div>
👇 자바스크립트
const categoryBtn = document.querySelector(".work__categories");
const projects = document.querySelectorAll(".project");
const projectContainer = document.querySelector(".work__projects");
categoryBtn.addEventListener("click", (e) => {
const selected = document.querySelector(".category__btn.selected");
// 기존 선택되었던 버튼 스타일링 삭제
selected.classList.remove("selected");
const target =
e.target.nodeName === "BUTTON" ? e.target : e.target.parentNode;
// 선택된 버튼에 스타일링
target.classList.add("selected");
/*
숫자버튼을 나타내는 span 태그를 클릭시 data-filter가 정의되어있지 않아 parentnode인
category__btn의 filter값을 가져온다.
*/
const filter = e.target.dataset.filter || e.target.parentNode.dataset.filter;
if (filter == null) return;
projectContainer.classList.add("anim-out");
setTimeout(() => {
projects.forEach((project) => {
/*
카테고리버튼의 filter값이랑 project이미지의 filter값이랑 같아야
front일때 front프로젝트, back일 때 back프로젝트만 보여짐
*/
if (filter === "*" || filter === project.dataset.type) {
project.classList.remove("invisible");
} else {
project.classList.add("invisible");
}
});
projectContainer.classList.remove("anim-out");
}, 300);
});
- 하나의 버튼에는
<button>
과<span>
이 함께있어서<button>
클릭시에는 바로 target을 지정해주면 되지만<span>
클릭 시에는 부모노드를 target으로 지정해주어야 한다.- 이미지들이 안보였다가 필터링 후 0.3초 뒤에 필터링된 이미지들이 새롭게 보여야하므로 setTimeout()안에서 필터링을 진행했다.
-> setTimeout()밖에서 진행할 시 이미지들이 필터링이 된 모습으로 보여졌다가 사라졌다가 다시 보이게 되어 애니메이션이 불편해보인다.- 카테고리버튼의 filter값이랑 project이미지의 filter값이랑 같아야 front일때 front 프로젝트만, back일 때 back프로젝트만 보여짐
미디어쿼리와 flex, position을 이용하여 스타일링하여 반응형으로 구현해보았다.
보편적인 모바일 사이즈인 768px이하일 때 navigation bar를 토글버튼으로 묶어서 구현하였고 전체적인 폰트사이즈, padding값을 수정하였다.
/* 768px 이하의 스크린에서 */
@media screen and (max-width: 768px) { ... }
더 자세한 코드들이 궁금하시다면?
이번에 홈페이지를 만들면서 마크업, 어떻게 div박스들끼리 묶을지 고민하는 과정이 큰 틀을 짜는데 중요한 초기과정이라는 걸 느꼈다. flex, position의 개념을 잡아가는데 좋은 경험이었고 각 브라우저 별 개발자모드에서 요소들의 정보를 파악하고 디버깅에 대해 경험했고 자바스크립트의 문법을 조금 더 공부해야겠다.