[12/26] TIL
😊오늘 배운 내용
팀 프로젝트 - 개인 페이지 구현
SQL 강의 select, from 까지
[개인 페이지 구현 코드]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Team Introduction</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Black+Han+Sans&family=Gowun+Dodum&family=IBM+Plex+Sans+KR&family=Nanum+Pen+Script&display=swap');
* {
font-family: "Nanum Pen Script", serif;
font-weight: 400;
font-style: normal;
}
/* Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-size: 30px;
background-image: url('https://aniland1.cafe24.com/studio_ghibri/download/img/ponyo.jpg');
/* 배경 이미지 URL */
background-position: center center;
/* 이미지 중앙에 위치 */
background-size: cover;
/* 화면 전체를 덮도록 크기 조정 */
background-repeat: no-repeat;
/* 이미지 반복하지 않도록 설정 */
color: #333;
line-height: 1.6;
}
/* Header */
.header {
background-color: rgba(174, 198, 207, 0.8);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 20px 0;
position: sticky;
top: 0;
z-index: 1000;
}
.header nav {
width: 90%;
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.header nav a {
font-weight: 600;
text-decoration: none;
color: #333;
margin: 0 15px;
transition: color 0.2s ease-in-out;
}
.header nav a:hover {
color: #0074ff;
}
/* Main Container */
.container {
width: 90%;
max-width: 1200px;
margin: 40px auto;
}
/* Profile Section */
.profile {
display: flex;
gap: 30px;
align-items: center;
margin-bottom: 40px;
}
.profile .image {
width: 355px;
height: 200px;
border-radius: 8px;
background-image: url('KakaoTalk_20241226_132325328.jpg');
background-position: center center;
background-size: contain;
/* background: linear-gradient(135deg, #0074ff, #00d1ff);
border-radius: 12px;
color: #ffffff;
font-size: 24px;
font-weight: bold; */
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.profile .info {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.profile .info div {
background-color: rgba(255, 255, 255, 0.8);
/* 배경을 투명하게 */
/* background-color: #ffffff; */
border: 1px solid #e6e8eb;
border-radius: 8px;
padding: 15px 20px;
font-size: 30px;
font-weight: 500;
color: black;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
/* opacity: 0.8; 텍스트 카드에 투명도 적용 */
}
/* Details Section */
.details {
display: flex;
justify-content: space-between;
gap: 20px;
margin-bottom: 40px;
}
.details div {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
background-color: rgba(255, 255, 255, 0.8);
/* 배경을 투명하게 */
/* background-color: #ffffff; */
border: 1px solid #e6e8eb;
border-radius: 8px;
text-align: center;
padding: 10px;
font-size: 40px;
font-weight: 600;
color: #333;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.details .rating {
flex: 0.5;
}
.details .name {
font-size: 70px;
}
.details .type {
font-size: 70px;
}
/* Abilities Section */
.abilities {
background-color: rgba(255, 255, 255, 0.8);
/* 배경을 투명하게 */
/* background-color: #ffffff; */
border: 1px solid #e6e8eb;
border-radius: 12px;
padding: 30px 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
margin-bottom: 40px;
}
.abilities .title {
font-size: 40px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.abilities .list div {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background-color: #f9fafb;
border-radius: 8px;
margin-bottom: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
font-size: 30px;
color: #555;
}
.abilities .list div span {
font-weight: 500;
font-size: 30px;
}
.abilities .list div:last-child {
margin-bottom: 10px;
}
.highlight {
font-weight: bold;
/* 글자를 굵게 */
font-size: 60px;
/* 글자 크기 크게 */
}
.form-control {
font-size: 20px;
/* 입력 텍스트 크기 */
height: 50px;
/* 입력 필드 높이 */
}
/* 라벨 높이 기준 가운데 정렬 */
.form-floating label {
display: flex;
align-items: center;
height: 100%;
/* 부모 요소 높이 기준으로 정렬 */
padding-left: 10px;
/* 좌측 여백 추가 (선택 사항) */
font-size: 25px;
/* 라벨 텍스트 크기 */
}
.mybtn {
margin-bottom: 30px;
}
</style>
<script type="module">
// Firebase SDK 라이브러리 가져오기
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";
import { getFirestore } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
import { collection, addDoc } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
import { getDocs } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
};
// Firebase 인스턴스 초기화
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
$("#postingbtn").click(async function () {
let name = $('#name').val();
let content = $('#content').val();
// 현재 시간 추가
let now = new Date();
let timestamp = now.toLocaleString(); // 시간 형식 변환 (YYYY-MM-DD HH:mm:ss)
let doc = {
'name': name,
'content': content,
'timestamp': timestamp, // 시간 필드 추가
};
await addDoc(collection(db, "profile"), doc);
alert('방명록을 남겼습니다!');
window.location.reload();
})
let docs = await getDocs(collection(db, "profile"));
docs.forEach((doc) => {
let row = doc.data();
console.log(row);
let name = row['name'];
let content = row['content'];
let timestamp = row['timestamp']; // 저장된 시간 가져오기
let temp_html = `<li class="list-group-item">${name} - ${content} <span style="float:right;">${timestamp}</span></li>`;
$('#postinglist').append(temp_html);
});
</script>
</head>
<body>
<header class="header">
<nav>
<a href="#">팀 소개 페이지</a>
<a href="#">팀원 1</a>
<a href="#">팀원 2</a>
<a href="#">팀원 3</a>
<a href="#">팀원 4</a>
<a href="#">팀원 5</a>
</nav>
</header>
<div class="container">
<div class="profile">
<div class="image"></div>
<div class="info">
<div><span class="highlight">각오 & TMI</span><br>열심히 해서 백엔드 개발자로 취뽀하자!<br>18살 강아지를 키우고 있어요🐶 </div>
</div>
</div>
<div class="details">
<div class="name">문유빈</div>
<div class="type">INTP</div>
<div class="rating">종합 능력치<br>⭐⭐⭐</div>
</div>
<div class="abilities">
<div class="title">능력치</div>
<div class="list">
<div><span>노력 ⭐⭐⭐⭐⭐</span><span>열심히 할 자신 있습니다!!!</span></div>
<div><span>지식 ⭐⭐⭐</span><span>전공자이지만 부족한 부분이 많습니다. 많이 노력하겠습니다!</span></div>
<div><span>깡 ⭐⭐</span><span>MBTI I라서 쑥쓰러움이 많습니다ㅎㅎ..</span></div>
</div>
</div>
<div class="abilities">
<div class="title">방명록</div>
<div class="mypostingbox" id="postingbox">
<div class="form-floating mb-3">
<input type="text" class="form-control" id="name" placeholder="이름을 입력해주세요">
<label for="name">이름</label>
</div>
<div class="form-floating mb-3">
<input type="text" class="form-control" id="content" placeholder="내용을 입력해주세요">
<label for="content">내용</label>
</div>
<div class="mybtn">
<button id="postingbtn" type="button" class="btn btn-dark">기록하기</button>
</div>
</div>
<ul class="list-group" id="postinglist">
</ul>
</div>
</div>
</body>
</html>
[어떤 문제가 있었는지]
css 속성을 다루는데 어려움이 있었다. 특정 태그를 지정해서 정렬하기, 이 div 태그가 어디까지의 영역이고 그 안에서 어떻게 정렬하고 폰트사이즈는 어떻게 키우는지 어려움이 있었다. 특히 헷갈렸던 건 정렬 부분이었다.
[내가 시도해본 것들]
일단 무작정 그 전에 썼던 코드들을 가져다 쓴다던가. html 태그에 id값 붙이고 직접 css 속성 적어가면서 봤었다.
[어떻게 해결했는지]
내가 시도해본 부분들이 너무 중구난방이라 이해를 돕기 위해서 gpt 사용을 많이 했다.
[무엇을 새롭게 알았는지]
css 정렬 부분을 정리해보았다.
display: flex;
해당 컴포넌트를 flex 컴포넌트 상태로 하겠다고 명시해주는 코드이다. 이걸 명시 해준 후 다른 flex 속성을 정의해가면서 정렬한다.
justify-content: space-between;
자식 요소들을 사이의 수평공간을 어떻게 할지 정의하겠다는 코드이다. space-between은 첫번째는 맨 왼쪽 정렬, 마지막은 맨 오른쪽 정렬, 나머지들은 왼쪽과 오른쪽 사이에 공평하게 나누어서 각자 배치하겠다는 의미이다. 이외에도,
- flex-start: 아이템들을 왼쪽으로 정렬합니다.
- flex-end: 아이템들을 오른쪽으로 정렬합니다.
- center: 아이템들을 가운데 정렬합니다.
- space-around: 아이템들 사이와 양 끝에 균등한 간격을 둡니다.
- space-evenly: 아이템들 사이와 양 끝에 동일한 간격을 둡니다.
align-items: center;
align-items는 자식요소들을 세로방향을 어떻게 정렬할지 정의하는 코드이다. center는 가운데로 정렬하겠다는 의미이고 높이가 다른 요소들도 가운데를 기준으로 정렬한다. 이외에도,
- flex-start: 자식 요소들을 위쪽으로 정렬합니다.
- flex-end: 자식 요소들을 아래쪽으로 정렬합니다.
- stretch: 자식 요소들이 컨테이너의 높이에 맞게 늘어납니다. (기본값)
- baseline: 텍스트의 **기준선(baseline)**에 맞춰 정렬됩니다.
flex: 1;
flex 속성은 자식요소의 크기조절 방식을 정의한다. flex 속성은 다음과 같이 이루어져 있다.
flex: <flex-grow> <flex-shrink> <flex-basis>;
- flex-grow: 남은 공간을 차지할 수 있는 비율.
- flex-shrink: 컨테이너가 작아질 때 줄어드는 비율.
- flex-basis: 아이템의 기본 크기.
- flex-grow: 1; → 남은 공간을 동일한 비율로 차지.
- flex-shrink: 1; → 공간이 부족할 경우 동일한 비율로 줄어듦.
- flex-basis: 0; → 아이템의 기본 크기를 0으로 설정.
이 flex 속성은 컨테이너 크기가 변화함에 따라 자식 요소들의 크기가 동적으로 맞춰져서 변화한다.
flex-direction: column;
기본적으로 Flexbox는 자식 요소를 가로 방향(row)으로 정렬하지만, flex-direction: column;은 이를 세로 방향(column)으로 정렬하도록 한다.
gap: 20px;
gap은 자식요소들 간 사이에 간격을 추가하는 속성이다.
따라서 이 코드를 예시로 설명하자면,
/* Details Section */
.details {
display: flex;
justify-content: space-between;
gap: 20px;
margin-bottom: 40px;
}
flex요소로 배치할것이며, 각 요소들을 수평으로 정렬할때 맨왼쪽은 왼쪽 정렬, 맨 오른쪽은 오른쪽 정렬, 나머지 것들은 공평하게 사이간격을 나눈다. 그리고 요소간 간격은 20px, margin-bottom은 40px으로 div 영역 내에서 아래쪽 margin이 40px만큼 생기게 한다는 의미이다.
그리고 강의에서 배운 내용 이외에 추가적을 구현한 내용이 있다. 방명록 기능을 구현할 때 이 방명록을 남긴 시간도 같이 남기고 싶어서 시간을 표시하는 기능도 구현했다.
// 현재 시간 추가
let now = new Date();
let timestamp = now.toLocaleString(); // 시간 형식 변환 (YYYY-MM-DD HH:mm:ss)
Date()를 이용해 객체를 생성하고 현재 날짜와 시간을 얻는다. 그리고 toLocaleString() 메서드를 사용하여, Date 객체를 사람이 읽기 쉬운 현지 시간 형식으로 변환한다.
그리고 데이터베이스 필드에 다음과 같이 추가한다.
let doc = {
'name': name,
'content': content,
'timestamp': timestamp, // 시간 필드 추가
};
따라서 방명록을 하나씩 남길때마다 남긴 시간도 뜨도록 구현했다.
[내일 할일]
내일은 SQL강의를 집중적으로 듣고 개인페이지 css를 조금 더 수정해야할 것 같다. 아마도 정렬부분과 색상이나 테마를 하나로 잡고 그에 맞게 디자인해야할 것 같다. 그리고 리드미 뼈대에 내용을 추가해야겠다! 오늘도 알찬 하루였다 😊👍