국비과정을 하면서 좋았던 점은, 웹의 전체적인 과정을 한번씩 훑어본다는 것이다.
안 좋았던 점 역시 마찬가지. 각 분야마다 훑어보기만 하지 깊숙하게 들어가거나 체득할 시간을 주지 않는다.
2번의 팀프로젝트를 진행하면서 프론트를 할 기회가 있었지만, 내가 맡은 포지션은 주로 백엔드쪽이었다.
프론트를 다루더라도 js위주로 다뤘지 css쪽은 거의 다루지 않았다.
그래서일까, 내가 개인프로젝트로 만든 것은 화면이 너무 밍밍한 나머지 어디 내놓기 민망할 정도였다.
이리저리 굴러다니는 웹땔감으로써, 백엔드를 지원하더라도 어차피 강제 풀스택이 되는 건 자명한 사실.
십중팔구 프론트를 만지게 될텐데, CSS를 못 다루니 큰일이다.
CSS를 배울 때는 이론보단 실습 위주로 하는 게 좋다고 하니, 따라하면서 배울 만한 강좌가 있나 둘러봤다.
그때 때마침 유데미에서 할인 행사가 진행 중이었고, 둘러보던 중에 하나 괜찮은 것을 발견했다.
제목은 "50 Projects In 50 Days - HTML, CSS & JavaScript".
제목대로 50가지 미니 프로젝트를 하면서 html-css-js를 익히는 강좌였다.
이론만 대충 알고 어떻게 다루는지 모르는 나한테 딱 맞았기 때문에, 후다닥 구매 버튼을 눌렀다.
참고로 해당 강좌의 url 주소: https://www.udemy.com/course/50-projects-50-days/
총 50개의 프로젝트에 대략 20시간 정도의 강의 길이였다.
강의 듣고 각 프로젝트 별로 실습한 후 블로그에 기록하는 것을 고려하면, 대략 80시간에서 100시간 이상을 투자해야 할 것 같다.
이 강의에서의 내 목표는, 하루에 프로젝트 1개씩 듣고 실습해보는 것이다.
그럼 바로 project1로 가즈앗!
---
첫번째 프로젝트의 이름은 "Expanding Cards" 다.
여러 장의 카드가 있고, 그 중에 하나를 클릭하면 해당 카드가 커진다(확장된다).이때, 원래 이미 확장된 카드가 있었다면, 그 카드를 원래 크기로 축소한 후, 클릭한 카드를 확장한다는 기능이다.
첫번째 프로젝트인 만큼 쉬운 난이도였고, 어디선가 많이 본 것 같은 로직이었다.
코드 이해하는 데 큰 문제는 없었기에 바로 응용에 들어갔다.
강좌에서는 5장의 카드를 수평으로 1세트 나열하는 것이라면, 내가 만드는 것은 총 10장의 카드를 수직으로 2세트(1세트 당 5장)씩 나열하는 것이다!
일단 결과물부터 바로 보도록 하자!

백엔드쪽 코드를 다룰 땐, 눈에 띄게 드러나는 결과물이 별로 없어서 뭔가 공장 돌아가는 느낌이었다.
그에 반면 프론트에서는, 코드를 작성하면 휙휙 결과물이 바뀌니, 만들면서 느끼는 뿌듯함 정도는 프론트 쪽이 확실히 더 강한 것 같다.
이번 프로젝트에서 중점적으로 배운 것은 flex 파트였는데,
flex에 대해 개념 정리하면서 적용하는 과정에서 문제가 발생했다.
강좌대로 flex를 수평으로 적용하면, flex-grow 수치가 제대로 적용되어 카드 간 비율이 수치에 맞게 나눠졌다.
마찬가지로 수직으로 적용하려면 flex-direction을 column으로 설정하기만 하면 되는 줄 알았다.
하지만 결과적으로 flex-grow가 적용 안 되면서 이미지가 하나도 나타나지 않는 에러가 발생했다.
왜 안 될까 이리저리 코드를 바꿔봤지만 이미지를 띄우는 데에 실패했다.
결국 flex-basis로 min-height를 일일이 설정해놓는 편법을 썼다.
.panel {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 45vw;
flex: 0.5 1 10vh;
flex-direction: column;
border-radius: 50px;
color: #fff;
cursor: pointer;
margin: 5px 0;
position: relative;
-webkit-transition: all 700ms ease-in;
}
.panel h3 {
font-size: 60px;
position: absolute;
bottom: 20px;
left: 20px;
margin: 0;
opacity: 0;
}
.panel.active1, .panel.active2 {
flex: 5 1 50vh;
}
각 카드별로 기본 height를 10vh씩 주는 것이다. 그리고 카드를 클릭하면 height를 50vh만큼 확장하는 로직이다.
내 평소 스타일이 결과물만 괜찮으면 상관없다는 작업방식이지만, 그래도 찜찜한 건 마찬가지였다.
특히 이런 기본기를 갈고닦는 실습일 때 꼼꼼히 배워야 나중에 써먹지 않겠는가.
그래서 인터넷에 검색해본 결과, 다행히 내가 원하는 답변을 스택오버플로우에서 손쉽게 찾을 수 있었다.
내용을 요약하면, 카드 panel의 부모 요소인 container의 height를 명시하지 않았기 때문에 공간이 없어서 카드panel이 안 보이는 것이었다...
스택오버플로우 주소:
html - flex-grow not working in column layout - Stack Overflow
그래서 panel의 부모 요소-container에 height를 100vh 추가했다.
.container1, .container2 {
display: flex;
flex-direction: column;
height: 100vh;
width : 50vw;
align-items: center;
margin: 0 auto;
}
.panel {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 45vw;
flex: 1;
flex-direction: column;
border-radius: 50px;
color: #fff;
cursor: pointer;
margin: 5px 0;
position: relative;
-webkit-transition: all 700ms ease-in;
}
.panel h3 {
font-size: 60px;
position: absolute;
bottom: 20px;
left: 20px;
margin: 0;
opacity: 0;
}
.panel.active1, .panel.active2 {
flex: 5;
}
success!
이번 실습을 하면서, flex를 세로로 하려면 height를 명시하여 공간을 확보해야 한다는 것을 배웠다.
아래는 내 전체 코드다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Expanding Cards</title>
</head>
<body>
<div class="container1">
<div class="panel active1" style="background-image: url('https://thumbnail.laftel.net/items/full/c900a268-2a33-49c2-ae9d-dd747f437782.jpg?c=0%2C559%2C1000%2C1122&webp=1&w=380')">
<h3>원치 않는 불사의 모험가</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/home/a45bdd6f-6934-4b23-a284-136d9dcb40ef.jpg?c=0%2C0%2C1024%2C576&webp=1&w=380')">
<h3>그 비스크 돌은 사랑을 한다</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/home/f60bcd03-0083-4827-b0ff-d19d0a850820.jpg?c=0%2C0%2C1024%2C576&webp=1&w=380')">
<h3>진격의 거인 1기</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/full/80979660-bcb1-46e5-a56f-6eb09c2fd131.jpg?c=0%2C297%2C588%2C627&webp=1&w=380')">
<h3>최강 탱커의 미궁 공략</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/home/5047bade-341f-43f8-b9d3-722280054cee.jpg?c=0%2C0%2C940%2C529&webp=1&w=380')">
<h3>귀멸의 칼날</h3>
</div>
</div>
<div class="container2">
<div class="panel active2" style="background-image: url('https://thumbnail.laftel.net/items/home/ebdfbb97-a05c-4d64-bd19-0ccd3f9cb364.jpg?c=0%2C0%2C832%2C468&webp=1&w=380')">
<h3>전생했더니 슬라임이었던 건에 대하여</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/home/27594cda-0966-4146-9ee3-28a7d4c1f3a4.jpg?c=0%2C0%2C1024%2C576&webp=1&w=380')">
<h3>귀멸의 칼날: 환락의 거리편</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/home/964de12d-cea3-4940-a843-6f49c3b9a497.jpg?c=0%2C0%2C640%2C360&webp=1&w=380')">
<h3>주술회전 1기 part2</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/home/827cf81b-aeb8-4fdb-aa7f-7c12e1ee56b6.jpg?c=0%2C0%2C640%2C360&webp=1&w=380')">
<h3>스파이 패밀리</h3>
</div>
<div class="panel" style="background-image: url('https://thumbnail.laftel.net/items/home/acc5935b-2657-423c-bdbc-39a461b6fb4e.jpg?c=0%2C0%2C640%2C360&webp=1&w=380')">
<h3>최애의 아이</h3>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
@import url('https://fonts.googleapis.com/css?family=Muli&display=swap');
* {
box-sizing: border-box;
}
body {
font-family: 'Muli', sans-serif;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
margin: 0;
}
.container1, .container2 {
display: flex;
flex-direction: column;
height: 100vh;
width : 50vw;
align-items: center;
margin: 0 auto;
}
.panel {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 45vw;
flex: 1;
flex-direction: column;
border-radius: 50px;
color: #fff;
cursor: pointer;
margin: 5px 0;
position: relative;
-webkit-transition: all 700ms ease-in;
}
.panel h3 {
font-size: 60px;
position: absolute;
bottom: 20px;
left: 20px;
margin: 0;
opacity: 0;
}
.panel.active1, .panel.active2 {
flex: 5;
}
.panel.active1 h3, .panel.active2 h3 {
opacity: 1;
transition: opacity 0.3s ease-in 0.4s;
}
@media (max-width: 480px) {
.container {
width: 100vw;
}
.panel:nth-of-type(4),
.panel:nth-of-type(5) {
display: none;
}
}
let panels = document.querySelectorAll(".panel");
console.log("panels: {}", panels);
panels.forEach((panel) => {
panel.addEventListener("click", () => {
const currentContainer = panel.parentElement;
// 먼저, 현재 컨테이너에서 모든 패널의 active1, active2를 제거.
removeActiveClasses(currentContainer, "active1", "active2");
const isActive =
panel.classList.contains("active1") ||
panel.classList.contains("active2");
// 클릭된 패널이 이미 active 가 아니라면, container에 따라 active를 추가
if (!isActive) {
if (currentContainer.classList.contains("container1")) {
panel.classList.add("active1");
} else if (currentContainer.classList.contains("container2")) {
panel.classList.add("active2");
}
}
});
});
function removeActiveClasses(container, ...activeClassesToRemove) {
activeClassesToRemove.forEach((activeClass) => {
container.querySelectorAll(`.${activeClass}`).forEach((panel) => {
panel.classList.remove(activeClass);
});
});
}
'JavaScript' 카테고리의 다른 글
소개 | Eloquent JavaScript 번역 (2) | 2024.11.07 |
---|---|
Progress Steps 구현하기(50 Projects In 50 Days 강의 실습 - 2) (0) | 2024.03.07 |