본문 바로가기
Front-End Study/나만의 프로젝트

나만의 페이지 만들기

by 코딩기 2024. 1. 7.
728x90

Pasted image 20240107220018.png

최근 코딩 자율학습단 5기를 끝마침과 더불어 직접 무언가를 만들어보고 싶지만 아직 초짜라는 것을 잘 알기에 간단한 것부터 차근차근 해보고싶어 만들어보았다.
CSS 적용 시 편의성을 위하여 방법론 중 하나인 컨테이너와 콘텐츠의 분리(OOCSS)를 최대한 생각
하며 작성하고자 했다.

<!-- 레이아웃 -->
<div class="main-container">
        <div class="main-wrapper">
<!-- 기본적으로 여러개의 div 클래스를 부모자식 관계로 선언, 콘텐츠와 레이아웃을 분리한다. -->
/* 기본 CSS */
*{
    padding:0;
    margin:0;
    box-sizing: border-box;
}

body{
    margin : 0 auto;
    width : 100%;
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-position: center;
    background-size: cover;
    color : white;
}

1. 시계

Text 부분을 단순히 00:00으로 채우고, 이를 JS의 Date 객체와 setInterval 함수를 이용하여 시간을 초마다 업데이트되게 만들어 시계를 구현

<!-- 시계 -->
<h2 id = clock>00:00</h2>
.main-wrapper #clock{
    font-size:6rem;
}

querySelector를 이용해 clock태그를 clock 변수로 가져오고, getClock 함수를 선언하여 Date
객체를 받아와 각 변수에 시, 분, 초를 할당 후 태그의 Text 부분에 setInterval 함수로 1초마다
업데이트 되도록 설정

const clock = document.querySelector("h2#clock"); // html 요소 가져오기

function getClock() {
    const date = new Date();
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");

    clock.innerText = `${hours}:${minutes}`;
}
getClock(); // 최초 시계 업데이트
setInterval(getClock, 1000); // 초마다 반복

2. 유저 인식

form 태그의 input 태그를 이용하여 유저의 이름을 입력받고, submit으로 전송하여 localStorage에 저장하는 형식으로 구현

<form id="login-form" class = "hidden">
	<input required 
	maxlength="15" 
	type="text" 
	placeholder="What is your name?">
	<input type="submit" value="Log In" id = "log-in">
</form>
<h1 id = greeting class="hidden"></h1> <!-- 유저 입력 후에 입력창이 사라지고 활성화 -->
.main-wrapper #login-form input{
    background-color : rgba(256,256,256,0.2);
    width: 300px;
    text-align: center;
    height:40px;
    border-radius: 20px;
    border-color: rgba(0,0,0,0.1);
    border: none;
}
.main-wrapper #login-form input::placeholder{
    font-size: 1rem;
    color: black;
}
.main-wrapper #login-form input:focus{
    outline: none;
    box-shadow: rgba(256,256,256,0.5);

}
.main-wrapper #login-form input#log-in{
    display: none;
}

const로 변수를 선언하여 html 태그를 불러오고, 자주 쓰는 string 요소 또한 변수 할당
addEventListener를 통해 활성화되는 요소마다 함수를 실행하여 동작할 수 있도록 구현

const loginForm = document.querySelector("#login-form");
const loginInput = document.querySelector("#login-form input");
const greeting = document.querySelector("#greeting");
const removeUser = document.querySelector("#remove-user");

const HIDDEN_CLASSNAME = "hidden";
const USERNAME_KEY = "username";

// submit 버튼을 click 시 loginform에 hidden 클래스가 추가되어 사라지고, localStorage에
// loginInput.value를 저장하도록 작성
function onLoginSubmit(event){ 
    event.preventDefault();
    loginForm.classList.add(HIDDEN_CLASSNAME);
    localStorage.setItem(USERNAME_KEY, loginInput.value);
    paintGreetings();
}

// localStorage에서 불러온 유저 이름을 변수에 할당하고, 시간별 인사를 함수로 구현하여 표현
function paintGreetings() {
    const username = localStorage.getItem(USERNAME_KEY);
    SayHifunc(username);
    greeting.classList.remove(HIDDEN_CLASSNAME);
}

// 시간별로 인사를 나누어 각 시간별 인사와 유저 이름을 출력하도록 설정
function SayHifunc(username){
    const currentHours = new Date().getHours();
    console.log(typeof currentHours);
    if(currentHours >= 6 && currentHours < 12 ){
        greeting.innerText = `Good Morning! ${username}.`;
    }
    else if(currentHours >= 12 && currentHours < 18){
        greeting.innerText = `Good Afternoon! ${username}.`;
    }else if(currentHours >= 18 && currentHours < 21){
        greeting.innerText = `Good Evening! ${username}.`;
    }else{
        greeting.innerText = `Good Night! ${username}.`;
    }
}

const savedUserName = localStorage.getItem(USERNAME_KEY); 

// 만약 localStorage에 유저 이름이 없을 경우, loginForm을 보여지게 설정, 이름이 있을 시 
// 그대로 인사 출력
if(savedUserName === null){
    loginForm.classList.remove(HIDDEN_CLASSNAME);
    loginForm.addEventListener("submit", onLoginSubmit);
}else{
    paintGreetings();
}

3. TODO 리스트

유저가 작성한 할일 리스트를 출력하도록 구현하고, 새로고침 하더라도 계속 유지되도록 이를 localStorage에 배열 형식으로 저장, 더불어 delete 버튼도 추가하여 삭제도 가능하도록 구현

<div class = "todo-container">
	<form id="todo-form">
		<input type="text" placeholder="Write a To Do and Press Enter" required>
	</form>
	<div class="todo-list">
		<ul id="todo-list"></ul>
	</div>
</div>
.main-wrapper .todo-container{
    justify-content: center;
    align-items: center;
    position:fixed;
    width:300px;
    top: 50px;
    right:20px;
}
.todo-container .todo-list{
    margin-top: 20px;
    color:white; 
}
.todo-container button{
    margin-left:5px;
    background-color: inherit;
    border:none;
}
.main-wrapper #todo-form input:focus{
    outline: none;
    box-shadow: rgba(256,256,256,0.5);
}
.main-wrapper #todo-form input{
    width: 250px;
    text-align: center;
    height:40px;
    border-radius: 20px;
    border-color: rgba(0,0,0,0.1);
    border: none;
    background-color: rgba(256,256,256,0.2);
}
.main-wrapper #todo-form input::placeholder{
    font-size:15px; 
    color:black;
}

localStrorage를 적극 활용하여 TODO 리스트의 출력, 저장, 삭제를 구현

const todoForm = document.getElementById("todo-form");
const todoList = document.getElementById("todo-list");
const todoInput = document.querySelector("#todo-form input");

const TODOS_KEY = "todos";

let toDos = [];

// toDos 배열을 선언하여 리스트 작성 시마다 추가하고, 이를 localStorage에 JSON.stringify() // 를 이용해 배열 형식으로 저장하고 꺼내쓸 수 있도록 작성
function saveToDos(){
    localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}

// 발생 event를 인자로 받아와 target의 부모 노드, 즉 TODO 리스트를 선택하여 삭제 가능하도록
// 구현
function deleteTodo(event){
    const li = event.target.parentNode;
    li.remove();
    toDos = toDos.filter((toDo) => toDo.id !== parseInt(li.id));
    saveToDos();
}

// ul태그만 작성되어 있는 시점에서 TODO 리스트 작성시마다 li태그가 생성되도록 구현
function paintTodo(newTodo) {
    const li = document.createElement("li");
    li.style.listStyle = `none`;
    li.id = newTodo.id;
    const span = document.createElement("span");
    span.innerText = newTodo.text;
    const button = document.createElement("button");
    button.innerText = "✅";
    button.addEventListener("click", deleteTodo);

    li.appendChild(span);
    li.appendChild(button);
    todoList.appendChild(li);
    li.style.marginTop = `10px`;
}

// TODO리스트를 작성하고 전송할때마다 toDos 배열에 추가되도록 구현
function handleTodoSubmit(event) {
    event.preventDefault();
    const newTodo = todoInput.value;
    todoInput.value = "";

    const newToDoObj = {
        text:newTodo,
        id:Date.now()
    };
    toDos.push(newToDoObj);
    paintTodo(newToDoObj);
    saveToDos();
}

todoForm.addEventListener("submit", handleTodoSubmit);

const savedToDos = localStorage.getItem(TODOS_KEY);

// localStorage에 TODO 리스트가 존재할 경우, 리스트 항목을 추가할 때마다 업데이트되도록 구현
if(savedToDos !== null){
    const parsedToDos = JSON.parse(savedToDos);
    toDos = parsedToDos;
    parsedToDos.forEach(paintTodo);
}

4. 명언

quoteList 라는 배열을 선언하여 명언을 넣어준 후, random() 함수를 이용해 인덱스를 무작위로 설정되도록 구현하여 명언이 랜덤하게 나오도록 구현

<div id="quote">
	<span></span>
	<span></span>
</div>
.main-wrapper #quote{
    position:fixed;
    bottom:0;
    left: 50%;
    transform: translateX(-50%);
    max-width : 800px;  
    padding : 0 10px;
    margin-bottom : 20px;
    text-align: center;
}
.main-wrapper #quote span{
    display:block;
    color:beige;
}
.main-wrapper div#quote span:nth-child(1){
    font-size: 20px;
}
const quoteList = [] // 생략

const quote = document.querySelector("#quote span:first-child");
const author = document.querySelector("#quote span:last-child");

function setQuote(){
    const todayQuote = quoteList[Math.floor(Math.random() * quoteList.length)];
    quote.innerText = todayQuote.quote;
    if(todayQuote.author !== ""){
        author.innerText = `- ${todayQuote.author} -`;
    }
}

function UpdateQuote(){
    setQuote();
    setInterval(setQuote, 5000);
}

UpdateQuote();

5. 날씨

API와 fetch()를 이용해 실시간으로 변경되는 날씨 정보를 전달받고 이를 태그를 통해 출력

<div id="weather">
	<img>
	<span></span>
	<span></span>
</div>
.main-wrapper #weather{
    background-color: rgba(256,256,256,0.1);
    justify-content: center;
    align-items: center;
    position:fixed;
    width:250px;
    height:330px;
    bottom: 30px;
    right:43px;
    border:solid 5px rgba(256,256,256,0.3);
    border-radius: 10px;
}
.main-wrapper #weather span{
    display: block;
    margin-top:10px;
}
.main-wrapper #weather img{
    border:none;
    width: 160px;
    height:160px;
}
.main-wrapper #weather span:nth-child(2){
    margin-top:10px;
    font-size:1.8rem;
}
.main-wrapper #weather span:last-child{
    font-size:1.5rem;
}

navigator를 이용해 내 위치정보(위도 & 경도)를 변수에 할당하고, 백틱과 API를 활용해 url을 작성한 후 변수 할당, fetch 함수를 이용해 날씨 정보를 받아옴과 더불어 openweathermap에서 제공하는 이모지를 활용해 변하는 날씨마다 img가 바뀌도록 구현

const API_KEY = "0e41118d3ed276d850c79fd62e832a9f"; // API 키

// getCurrentPosition의 인자 중 하나, 속도나 정확도 등을 설정
var options = { 
    enableHighAccuracy: false,
    timeout: 5000,
    maximumAge: 0,  
  };

// 위도와 경도를 할당하고 이를 url에 할당하여 API를 통한 날씨 정보를 받아옴
function onGeoOk(position){
    const lat = position.coords.latitude;
    const lng = position.coords.longitude;
    const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${API_KEY}&units=metric`;
    fetch(url)
    .then((response) => response.json())
    .then((data) => {
        const iconUrl = `https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png`
        const weather = document.querySelector("#weather img:nth-child(1)");
        const city = document.querySelector("#weather span:nth-child(2)");
        const degree = document.querySelector("#weather span:last-child");
        city.innerText = data.name;
        weather.src = iconUrl;
        degree.innerText = `${data.main.temp}`;
    });
}

// 위치 정보를 받아오지 못할 경우
function onGeoError(){
    alert("Can't find you. No weather for you.");
}

navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError, options)

마무리

간단한 것이지만 스타트라인에서 시도한 나쁘지 않은 작업이었다고 생각한다.
더욱 나중에 다시 이 글을 봤을 때 이때 왜 이거밖에 못했지… 라고 생각할 수 있도록 정진하자~