오늘은 니꼬쌤의 바닐라 js로 momentum 클론 코딩을 해따
모멘텀앱이 궁금한 사람은
https://chrome.google.com/webstore/detail/momentum/laookkfknpbbblfpciffpaejjkokdgca?hl=ko
다운 받아 보세욥!
오직 CSS, HTML, Javascript만 사용해서 웹페이지를 만들어 보았다.
1. 폴더 구조
2. index.html
<!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="css/style.css">
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap"
rel="stylesheet"
/>
<title>Momentum</title>
</head>
<body>
<div class="scene">
<!-- Background -->
<div class="full-background"></div>
<div id="contents">
<div id="top">
<div>
<button id="todo-list-btn">
<i class="far fa-list-alt"></i>
</button>
<span id="todo-span" class="hidden">TO DO</span>
</div>
<!-- TODO FORM -->
<div id="modal" class="modal-overlay hidden">
<div class="modal-window hidden" id="modal-todo-list">
<form id="todo-form">
<input type="text" placeholder="Write a To Do and Pres Enter" required/>
</form>
<!-- TODO LIST -->
<ul id="todo-list"></ul>
</div>
</div>
<!-- WEATHER -->
<div id="weather">
<span></span>
<span></span>
</div>
</div>
<div id="main">
<!-- CLOOCK -->
<div id="clock">00:00:00</div>
<div id="today-info"></div>
<!-- LOGIN -->
<form class="hidden" id="login-form">
<input required maxlength="15" type="text" placeholder="What is your name?"/>
<input type="submit" value="LOGIN" id="login-button"/>
</form>
<h1 id="greeting" class="hidden"></h1>
<button class="hidden" id="logout-button">LOGOUT</button>
</div>
<!-- Quote -->
<div id="quote">
<span></span>
<span></span>
</div>
</div>
</div>
<script src="js/greeting.js"></script>
<script src="js/clock.js"></script>
<script src="js/quotes.js"></script>
<script src="js/background.js"></script>
<script src="js/todo.js"></script>
<script src="js/weather.js"></script>
<script
src="https://kit.fontawesome.com/eb1c9c2a9c.js"
crossorigin="anonymous"
></script>
</body>
</html>
<head> 태그 안에 <link>태그를 사용하여 폰트 스타일과 css/style.css를 불러오자.
<body> 태그 마지막에 <script>태그를 사용하여 js파일들과 Todo리스트 버튼에 사용할 버튼을 불러와주자
자! 그럼 본격적으로 위에서부터 차례대로 자바스크립트 파일을 뜯어보자!
3. js/background.js
const images = [
"3.jpeg",
"4.jpeg",
"5.jpeg",
"6.jpeg",
"7.jpeg",
"8.png",
];
//이미지 랜덤으로 뽑기
const chooseImage = images[Math.floor(Math.random() * images.length)];
//createElement("img")를 통해 html에 <img>태그 추가
const bgImage = document.createElement("img");
//querySelector .은 class가 full-background를 찾는 것
const container = document.querySelector(".full-background");
bgImage.src = `img/${chooseImage}`;
//container 제일 마지막에 bgImage를 추가한다
container.appendChild(bgImage);
// 반응형
function handleWindow(e) {
const windowWidth = e.target.innerWidth;
const windowHeight = e.target.innerHeight;
const broswerRatio = windowWidth / windowHeight;
const imageRatio = 1920 / 1080;
if (imageRatio > broswerRatio) {
container.style.height = "100%";
container.style.width = `${windowHeight * imageRatio}px`;
container.style.left = `${(windowWidth - windowHeight * imageRatio) / 2}px`;
container.style.top = "0";
} else {
container.style.height = `${windowWidth / imageRatio}px`;
container.style.width = "100%";
container.style.left = "0";
container.style.top = `${(windowHeight - windowWidth / imageRatio) / 2}px`;
}
}
window.addEventListener("resize", handleWindow);
window.dispatchEvent(new Event("resize")); // 강제로 resize 이벤트 발생 시킴
대부분 스타일을 위한 코드들이다. 여기서 중요한 코드는 document.querySelector와 document.createElement이다
먼저 document.querySelector는 html파일에서 특정 아이디, 클래스를 가진 태그를 찾는 것이다.
querySelector("") ""안에 들어올 것으로 #과 .와 태그가 올 수 있다.
class를 찾을 때에는 위처럼 .클래스명을 넣어주면 되고 id로 검색할 때는 #id명을 넣어주면 된다.
클래스명과 id명 뒤에 찾을 태그를 입력해주면 된다.
예를 들어, document.querySelector("#full-screen span:first-child") 이런식으로 하면 된다.
document.createElement("img")는 html에 <img>태그를 추가해준다는 것이다. img 뿐아니라 여러 <span> <button>등 다양한 태그가 올 수 있다.
appendChild와 prependChild가 있다. append는 그 태그의 가장 뒤에 추가하는 것이고 prepend는 그 태그 가장 앞에 추가해준다.
4. js/todo.js
const toDoForm = document.querySelector("#todo-form");
const toDoInput = document.querySelector("#todo-form input"); // toDoForm.querySelector("input"); 이렇게 써도 됨
const toDoList = document.querySelector("#todo-list");
const toDoListModal = document.querySelector("#modal-todo-list");
const toDoListBtn = document.querySelector("#todo-list-btn");
const modal = document.querySelector("#modal");
const todoSpan = document.querySelector("#todo-span");
const TODOS_KEY = "todos";
const TODOFORM_HIDDEN = "hidden";
const MODAL_OVERLAY = "modal-overlay";
let toDos = [];
toDoListBtn.addEventListener("mouseover", (e) => {
todoSpan.classList.remove(TODOFORM_HIDDEN);
});
toDoListBtn.addEventListener("mouseout", (e) => {
todoSpan.classList.add(TODOFORM_HIDDEN);
});
toDoListBtn.addEventListener("click", (e) => {
toDoListModal.classList.remove(TODOFORM_HIDDEN);
modal.classList.remove(TODOFORM_HIDDEN);
});
modal.addEventListener("click", (e) => {
const eventTarget = e.target;
if (eventTarget.classList.contains(MODAL_OVERLAY)) {
toDoListModal.classList.add(TODOFORM_HIDDEN);
modal.classList.add(TODOFORM_HIDDEN);
}
});
function saveToDos() {
localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}
function deletdToDo(event) {
const li = event.target.parentElement;
li.remove();
toDos = toDos.filter((toDo) => toDo.id !== parseInt(li.id));
saveToDos();
}
function paintToDo(newTodo) {
const li = document.createElement("li");
li.id = newTodo.id;
const span = document.createElement("span");
span.innerText = newTodo.text;
const button = document.createElement("button");
button.innerText = "✖";
button.addEventListener("click", deletdToDo);
li.appendChild(span);
li.appendChild(button);
toDoList.appendChild(li);
}
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);
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
toDos = parsedToDos;
parsedToDos.forEach(paintToDo);
}
제일 위에 부분에서 querySelector를 통해 태그들의 위치를 변수에 저장해준다.
여기서 중점적으로 볼 것은 addEventLister와 todolist 입력과 출력이다.
toDoListBtn.addEventListener("click", (e) => {
toDoListModal.classList.remove(TODOFORM_HIDDEN);
modal.classList.remove(TODOFORM_HIDDEN);
});
toDoListBtn에 click이벤트가 발생하면 toDoListModal의 클래스 TODOFORM_HIDDEN(="hidden")을 지운다.
즉, 보이게 한다는 것이다.
modal의 클래스에 TODOFORM_HIDDEN(="hidden")지운다. 이것 또한, 보이게 한다.
//toDoListBtn에 마우스 올렷을 때
toDoListBtn.addEventListener("mouseover", (e) => {
todoSpan.classList.remove(TODOFORM_HIDDEN);
});
//toDoListBtn에 마우스 내렸을 때
toDoListBtn.addEventListener("mouseout", (e) => {
todoSpan.classList.add(TODOFORM_HIDDEN);
});
//toDoListBtn 클릭했을 때
toDoListBtn.addEventListener("click", (e) => {
toDoListModal.classList.remove(TODOFORM_HIDDEN);
modal.classList.remove(TODOFORM_HIDDEN);
});
//modal클릭했을 때
modal.addEventListener("click", (e) => {
const eventTarget = e.target;
if (eventTarget.classList.contains(MODAL_OVERLAY)) {
toDoListModal.classList.add(TODOFORM_HIDDEN);
modal.classList.add(TODOFORM_HIDDEN);
}
});
//toDoForm 제출 했을 때 아래에서 설명
toDoForm.addEventListener("submit", handleToDoSubmit);
이런식으로 여러 이벤트에 대해 정의를 해준다.
toDoForm.addEventListener("submit", handleToDoSubmit);
function handleToDoSubmit(e) {
//새로고침 방지
e.preventDefault();
const newTodo = toDoInput.value;
toDoInput.value = "";
const newTodoObj = {
text: newTodo,
id: Date.now(),
};
toDos.push(newTodoObj);
paintToDo(newTodoObj);
saveToDos();
}
function paintToDo(newTodo) {
const li = document.createElement("li");
li.id = newTodo.id;
const span = document.createElement("span");
span.innerText = newTodo.text;
const button = document.createElement("button");
button.innerText = "✖";
button.addEventListener("click", deletdToDo);
li.appendChild(span);
li.appendChild(button);
toDoList.appendChild(li);
}
function saveToDos() {
localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}
saveToDos는 로컬스토리지에 TODOS_KEY(="todos") 키에 JSON.stringfy(toDos)라는 값을 저장해준다.
보면 로컬스토리지에 잘 나오는 것을 확인할 수 있다.
paintToDo는 <li>, <span>, <button> 태그를 생성하고 그 태그 안에 입력 받은 값을 넣어준다.
이렇게 입력을 구현했다.
삭제
function deletdToDo(event) {
const li = event.target.parentElement;
li.remove();
toDos = toDos.filter((toDo) => toDo.id !== parseInt(li.id));
saveToDos();
}
위 paitnToDo함수를 보면 버튼 클릭시 deltedToDo함수를 실행시킨다.
이 함수는 누른 타겟의 부모 <li>태그를 찾고, 누른 타겟의 아이디와 일치하는 아이는 toDos에서 삭제시킨다.
const savedToDos = localStorage.getItem(TODOS_KEY);
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
toDos = parsedToDos;
parsedToDos.forEach(paintToDo);
}
로컬 스토리지에 ToDos를 저장하였기 때문에, 나갔다 들어왔을 때에 로컬스토리지를 읽어서 ToDos를 받아올 수 있도록 만들었다.
5. js/weather.js
const API_KEY = "~~비밀입니다~";
function onGeoOk(position) {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
console.log("you live in", lat, lng);
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${API_KEY}&units=metric`;
// console.log(url);
fetch(url).then((resposne) =>
resposne.json().then((data) => {
const weather = document.querySelector("#weather span:first-child");
const city = document.querySelector("#weather span:last-child");
city.innerText = data.name;
weather.innerText = `${data.weather[0].main} / ${data.main.temp}`;
})
);
}
function onGeoError() {
alert("I can't find u. No weather for you");
}
navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError);
첫줄의 API_KEY는 비밀이다.
weather 코드는
https://openweathermap.org/current
이것을 보고 따라하길 바란다. 👊
6. js/clock.js
const clock = document.querySelector("#clock");
const todayInfo = document.querySelector("#today-info");
clock.innerText = new Date();
const getDay = function () {
const date = new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const days = date.getDay();
const weaks = ["일", "월", "화", "수", "목", "금", "토"];
todayInfo.innerText = `${year}년 ${month}월 ${day}일 ${weaks[days]}요일`;
};
const getClock = function () {
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}`;
};
getClock();
getDay();
setInterval(getClock, 1000);
clock은 매우 간단하다!
자바스크립트의 Date 객체를 이용하여 현재 시간을 불러온다.
그리고 setInval을 이용하여 1000ms마다 즉, 1초마다 getClock함수를 실행한다.
!! 막간 상식
setInterval(함수, ?) : ?ms마다 함수 실행 > 1초마다 실행하는 함수 > 시계
setTimeout(함수, ?) : ?ms후에 함수 실행 > 스톱워치, 알람
7. js/greeting.js
const loginInput = document.querySelector("#login-form input");
const loginForm = document.querySelector("#login-form");
const greeting = document.querySelector("#greeting");
const logout = document.querySelector("#logout-button");
const HIDDEN_CLASSNAME = "hidden";
const USERNAME_KEY = "username";
const onLoginSubmit = (e) => {
e.preventDefault();
loginForm.classList.add(HIDDEN_CLASSNAME);
const username = loginInput.value;
localStorage.setItem(USERNAME_KEY, username);
painGreetings(username);
showLogoutForm();
};
const painGreetings = function (username) {
greeting.classList.remove(HIDDEN_CLASSNAME);
greeting.innerText = `Hello ${username}`;
};
const showLoginForm = function () {
loginInput.value = "";
localStorage.clear();
logout.classList.add(HIDDEN_CLASSNAME);
greeting.classList.add(HIDDEN_CLASSNAME);
loginForm.classList.remove(HIDDEN_CLASSNAME);
};
logout.addEventListener("click", (e) => {
logout.classList.remove(HIDDEN_CLASSNAME);
showLoginForm();
});
const showLogoutForm = function () {
logout.classList.remove(HIDDEN_CLASSNAME);
};
const savedUsername = localStorage.getItem(USERNAME_KEY);
if (savedUsername === null) {
loginForm.classList.remove(HIDDEN_CLASSNAME);
loginForm.addEventListener("submit", onLoginSubmit);
} else {
painGreetings(savedUsername);
showLogoutForm();
}
위의 todo.js를 이해했다면 정말 이해하기 쉽다!
먼저 제일 위에 각 class와 id를 찾아 변수에 저장한다.
로그인도 투두리스트와 마찬가지로 접속할 때마다 로그인하기 귀찮으니 로컬 스토리지에 저장해서 구현했다.
const savedUsername = localStorage.getItem(USERNAME_KEY);
if (savedUsername === null) {
//show the form
loginForm.classList.remove(HIDDEN_CLASSNAME);
loginForm.addEventListener("submit", onLoginSubmit);
} else {
//show the greeting
painGreetings(savedUsername);
showLogoutForm();
}
로컬스토리지에 저장된 이름이 있다면 painGreetings함수 와 showLogoutForm함수를 실행시킨다.
painGreetings함수는 아래와 같이 인삿말을 출력하는 함수고 showLogoutForm함수는 로그아웃 버튼이 생기는 함수이다.
const onLoginSubmit = (e) => {
e.preventDefault();
loginForm.classList.add(HIDDEN_CLASSNAME);
const username = loginInput.value;
localStorage.setItem(USERNAME_KEY, username);
painGreetings(username);
showLogoutForm();
};
로그인이 안돼있을 시에는 LoginForm이 보이고 이름을 적고 엔터를 치면 onLoginSubmit함수가 실행된다.
onLoginSubmit함수는 새로고침 방지, 로컬 스토리지에 이름 저장, 인삿말 출력, 로그아웃 버튼 출력 기능이 있다.
const showLoginForm = function () {
loginInput.value = "";
localStorage.clear();
logout.classList.add(HIDDEN_CLASSNAME);
greeting.classList.add(HIDDEN_CLASSNAME);
loginForm.classList.remove(HIDDEN_CLASSNAME);
};
logout.addEventListener("click", (e) => {
logout.classList.remove(HIDDEN_CLASSNAME);
showLoginForm();
});
logout 클릭시 showLoginForm함수를 실행한다.
이 함수는 로컬스토리지 삭제, 로그아웃 버튼 비활성화, 안녕 문구 비활성화, 로그인 폼 활성화 기능이 있다.
8. js/quote.js
const quotes = [
{
quote: "JUST DO IT !",
author: "NIKE",
},
{
quote: "JUST DO IT !!",
author: "NIKE",
},
];
const quote = document.querySelector("#quote span:first-child");
const author = document.querySelector("#quote span:last-child");
//랜덤으로 뽑기
const todaysQuote = quotes[Math.floor(Math.random() * quotes.length)];
//명언 출력
quote.innerText = todaysQuote.quote;
author.innerText = todaysQuote.author;
매우 간단하다 !
명언들을 적고 랜덤으로 출력해준다!
필자는 저 명언을 너무 좋아하여 두개 밖에 안했지만, 독자들은 여러개 추가해서 테스트해보기를 권한다!
원래 알던 것도 있고, 처음 알았던 것도 있었다.
리액트에서 잘 이해되지 않던 코드가 오늘 클론 코딩을 하고 난 후에 완벽히는 아니지만 이해가 되는 것을 보고 왜 기본이 중요하다는 지 깨달았다.
오늘도 열코!!!!!!!!
'Javscript' 카테고리의 다른 글
[Javascript] Math 객체 (1/20) (0) | 2022.01.21 |
---|---|
[Javascript] 자바스크립트 (1/20) (0) | 2022.01.21 |
[Javascript] 자바스크립트 (1/19) (0) | 2022.01.20 |
[Javascript] 자바스크립트 (1/17) (0) | 2022.01.17 |
[Javascript] 자바스크립트 (1/16) (0) | 2022.01.17 |