프로젝트명
투두리스트 (ToDoList)
기간 / 인원
23.11.26 ~ 23.11.30 (5일) / 1인
소개
Vanilla JS(바닐라 자바스크립트)를 사용한 투두리스트(ToDoList) 사이트입니다.
배포 링크
🔗 ToDoList (Vercel로 배포하였습니다.)
크롬 앱 만들기
to-do-list-red-nu.vercel.app
Github 링크
https://github.com/ghvhdh333/ToDoList
GitHub - ghvhdh333/ToDoList: 바닐라 JS로 ToDoList 만들기
바닐라 JS로 ToDoList 만들기. Contribute to ghvhdh333/ToDoList development by creating an account on GitHub.
github.com
기술 스택
[ FE ]
Vanilla JS, HTML, CSS
[ Environment ]
VScode, Git, Github, Vercel
회고
로그인 기능 구현
// login.js
const loginForm = document.querySelector("#login-form");
const loginInput = document.querySelector("#login-form input");
const greeting = document.querySelector("#greeting");
const HIDDEN_CLASSNAME = "hidden";
const USERNAME_KEY = "username"
function onLoginSubmit(event) {
event.preventDefault();
loginForm.classList.add(HIDDEN_CLASSNAME);
const username = loginInput.value;
localStorage.setItem(USERNAME_KEY, username);
paintGreetings(username);
}
function paintGreetings(username){
greeting.innerText = `Hello! ${username} :)`;
greeting.classList.remove(HIDDEN_CLASSNAME);
}
const savedUsername = localStorage.getItem(USERNAME_KEY);
if(savedUsername === null){
// 유저 이름을 입력하지 않은 상태이면
loginForm.classList.remove(HIDDEN_CLASSNAME);
loginForm.addEventListener("submit", onLoginSubmit)
} else {
// 유저 이름을 입력된 상태이면
paintGreetings(savedUsername);
}
- input에 값을 입력하고 로그인 버튼을 눌리면 button의 submit 이벤트를 실행되어 브라우저가 자체적으로 새로고침을 하기 때문에 로그인이 되지 않으므로, 브라우저 자체적으로 새로고침 하는 것을 방지하기 위해 preventDefault()를 사용하여 새로고침을 방지합니다.
- 로그인을 하고 유저가 인위적으로 새로고침을 하면, 그 또한 로그인이 풀리기 때문에 유저의 이름을 기억하기 위해 localStorage를 통해 유저의 정보를 기억합니다.
LocalStorage를 이용한 ToDoList 상태관리
- toDoForm의 submit 이벤트로 인해 페이지가 새로고침 되는 것을 방지하기 위하여 preventDefault를 사용하였습니다.
- input 값을 submit 하면 값을 newTodo에 할당하고, input box의 값을 빈문자열("")로 초기화하였습니다.
- newToDoObj의 객체에 id 값을 Date.now()를 사용하여 밀리 초까지 나오는 숫자를 활용하였습니다.
- 로컬스토리지에 데이터를 저장할 때 JSON.stringify로 객체를 JSON 포맷의 문자열로 변환시키고 저장하였습니다.
- 데이터를 화면에 보여줄 때 저장된 로컬스토리지의 데이터를 JSON.parse를 하여 다시 객체로 변환시켜 화면에 나타냈습니다.
- check box의 체크유무에 따라 checked 클래스를 추가, 제거하여, 유저가 알 수 있게 CSS 효과를 넣었습니다.
// todo.js
const toDoForm = document.querySelector("#todo-form");
const toDoInput = document.querySelector("#todo-form input");
const toDoList = document.querySelector("#todo-list");
// todolist 저장할 배열
let toDos = [];
// JSON.stringify를 통해 객체를 JSON 포맷의 문자열로 변환시키고, localStorage에 저장한다.
function saveToDos () {
localStorage.setItem("todos", JSON.stringify(toDos));
}
// filter를 사용하여 'x'버튼을 누르면 해당 버튼의 상위 <li> 태그를 제외한 다른 todolist를 추출하여
// toDos 배열에 재할당하고, 재할당 된 것을 토대로 다시 JSON 포맷의 문자열로 변환시키고, 로컬스토리지에 저장한다.
function deleteToDo (event) {
const li = event.target.parentElement;
li.remove();
toDos = toDos.filter(el => el.id !== Number(li.id));
saveToDos();
}
// handleToDoSubmit를 통해 받은 newToDoObj 객체를 각 분야 알맞게 값을 할당하고,
// 체크유무에 따라 checked 클래스를 추가, 제거하여, 체크효과를 css로 넣어주며, 화면에 나타낸다.
function paintToDo(newToDoObj){
const list = document.createElement("li");
list.id = newToDoObj.id;
const checkbox = document.createElement("input")
checkbox.type = 'checkbox';
checkbox.classList = 'checkBox';
checkbox.addEventListener('click', handleCheckbox);
const span = document.createElement("span");
span.classList = 'toDoSpan';
span.innerText = newToDoObj.text;
// 기존의 체크박스의 체크 유무에 대해 보여주는 화면 분기
if(newToDoObj.checked) {
span.classList.add('checked');
checkbox.checked = true;
} else {
span.classList.remove('checked');
checkbox.checked = false;
}
function handleCheckbox(event){
const listChecked = event.target.checked;
if(listChecked){
span.classList.add('checked');
newToDoObj.checked = listChecked;
saveToDos();
} else {
span.classList.remove('checked');
newToDoObj.checked = listChecked;
saveToDos();
}
}
const div = document.createElement("div");
const button = document.createElement("button");
button.innerText = "❌";
button.addEventListener("click", deleteToDo);
div.appendChild(checkbox);
div.appendChild(span);
list.appendChild(div);
list.appendChild(button);
toDoList.appendChild(list);
}
// input의 submit시 새로고침 되는 것을 방지하기 위해 preventDefault()를 사용하고,
// newTodo에 사용자로부터 입력받은 값을 할당하고, 초기화 시킨다.
// 해당 input 값과, id, 체크유무를 newToDoObj에 할당하고 toDos 배열에 push하고,
// JSON.stringify를 통해 객체를 JSON 포맷의 문자열로 변환시키고, localStorage에 저장한다.
function handleToDoSubmit(event) {
event.preventDefault();
const newTodo = toDoInput.value;
toDoInput.value = "";
const newToDoObj = {
id : Date.now(),
text : newTodo,
checked : false,
}
toDos.push(newToDoObj);
paintToDo(newToDoObj);
saveToDos();
}
// toDoForm의 input에 값을 입력하고 엔터를 누르면
// submit 이벤트와, handleToDoSubmit 함수가 실행되도록 한다.
toDoForm.addEventListener("submit", handleToDoSubmit);
// saveToDos 함수를 통해 저장된 (객체를 JSON 포맷의 문자열로 변환된) 값을 불러온다.
const savedToDos = localStorage.getItem("todos");
// savedToDos를 통해 불러온 값이 있으면
// JSON.parse를 통해 JSON 포맷의 문자열을 객체로 변환하고,
// 모든 요소들을 paintToDo 함수를 통해 화면에 나타낸다.
if(savedToDos) {
const parsedToDos = JSON.parse(savedToDos);
toDos = parsedToDos;
parsedToDos.forEach(el => paintToDo(el))
}
랜덤 배경화면 & 명언 구현
- Math.floor(Math.random() * arr.length) 를 사용하여 랜덤 한 숫자를 구하고,
그 숫자를 사용하여 배경화면과, 명언을 무작위로 나타나게 하였습니다.
// background.js
const images = [
"0.jpg",
"1.jpg",
"2.jpg",
"3.jpg",
"4.jpg",
"5.jpg",
]
// 0 ~ images.length 까지 랜덤한 숫자를 발생시킨다.
const chosenImage = images[Math.floor(Math.random() * images.length)];
const bgImage = document.createElement("img");
bgImage.src = `img/${chosenImage}`;
document.body.appendChild(bgImage);
// quotes.js
const quotes = [
{
quote : "Don't dwell on the past.",
},
{
quote : "Believe in yourself.",
},
{
quote : "Follow your heart.",
}, ...
];
const quoteId = document.querySelector("#quote span:first-child");
// 0 ~ quotes.length 만큼 랜덤한 숫자를 반환해 줌
const todaysQuote = quotes[Math.floor(Math.random() * quotes.length)];
quoteId.innerText = todaysQuote.quote;
현재 날짜, 시간 기능 구현
- new Date()를 사용하여 현재 날짜와 시간을 구하였고,
padStart()를 이용하여 1자리 숫자일 때 '1'초가 아닌 '01'초가 되도록 구현하였습니다.
// clock.js
const clock = document.querySelector('#clock');
const date = document.querySelector('#date');
function getClock (){
const date = new Date();
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
clock.innerText = `${hours}:${minutes}:${seconds}`;
}
function getDate() {
const getdate = new Date().toLocaleDateString();
const slicedDate = getdate.slice(0, getdate.length -1);
date.innerText = slicedDate;
}
getClock();
getDate();
setInterval(getClock, 1000);
사용자의 현재 위치를 기반으로 한 도시이름, 날씨를 알려줌
- navigator.geolocation.getCurrentPosition()을 사용하여 현재 위치에 대한 위도, 경도 등을 구하였고,
윗 방법으로 구한 위도, 경도를 날씨 API (openweathermap.org)를 통해 사용자가 현재 위치하고 있는 도시와 날씨 등을 알려줍니다.
(API KEY는 공개하지 않으므로, ~~~ 로 대체하였습니다.)
// weather.js
const API_KEY = '~~~';
function onGeoOk(position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const URL = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`;
fetch(URL)
.then(res => res.json())
.then(data => {
const city = document.querySelector("#weather span:nth-child(1)");
const weather = document.querySelector("#weather span:nth-child(2)");
const temp = document.querySelector("#weather span:nth-child(3)");
const wind = document.querySelector("#weather span:nth-child(4)");
city.innerText = data.name;
weather.innerText = `/ ${data.weather[0].main}`;
temp.innerText = `/ ${data.main.temp} ℃`;
wind.innerText = `/ ${data.wind.speed} m/s`;
});
}
function onGeoError(){
alert("Can't find you. No weather for you.");
}
navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError);
부족한 점
- CSS 애니메이션 숙련도 부족 : ToDo의 List를 더욱 예쁘게 움직이고 싶었으나, 마음대로 되지 않았다.