TIL - JS MomentumClone

Seuling·2022년 5월 18일
0

TIL

목록 보기
27/30
post-thumbnail

바닐라JS로 Momentum 익스텐션 앱 클론!

구현과정

기본 html 구조

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sseul's_Momentum</title>
    <link rel="stylesheet" href="./CSS/style.css" />
  </head>
  <body>
    <form id="login-form" class="hidden">
      <input
        required
        maxlength="15"
        type="text"
        placeholder="What is your name?"
      />
      <input type="submit" value="Log In" />
    </form>
    <div class="main-box">
      <div class="clockAndName">
        <h2 id="clock">00:00</h2>
        <h1 id="greeting" class="hidden"></h1>
      </div>
      <form id="todo-form">
        <p>"What is your main focus for today??"</p>
        <input type="text" required />
      </form>
      <ul id="todo-list"></ul>
    </div>
    <div id="quote"><span></span><br /><span></span></div>
    <div id="weather"><span></span><span></span></div>
    <script src="./JS/greetings.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>
  </body>
</html>

기본 CSS 구조

* {
  color: white;
}
/* 초기화 */
h1,
h2 {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
}

button {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  background-color: transparent;
}

input {
  width: 100%;
  height: 100%;
  text-align: center;
  font-size: 20px;
  outline: none;
  border: none;
  border-bottom: 1px solid white;
  color: white;
  background-color: transparent;
}

input::placeholder {
  color: white;
}
/* 시작 */
.hidden {
  display: none;
}

img {
  size: 100%;
}
body {
  height: 100vh;
  background-repeat: no-repeat;
  background-size: cover;
  overflow: hidden;
  text-align: center;
}

h2 {
  font-size: 150px;
}

h1 {
  font-size: 50px;
}

.main-box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.clockAndName {
  margin-bottom: 50px;
}

#todo-form p {
  font-size: 30px;
}

#todo-list li span {
  font-size: 20px;
}

#quote {
  position: absolute;
  bottom: 1%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#weather {
  text-align: right;
}

1. background image 랜덤하게 가져오기

const bgImages = ["0.jpeg", "1.jpeg", "2.jpeg", "3.jpeg", "4.jpeg", "5.jpeg"];
const selectedImg = bgImages[Math.floor(Math.random() * bgImages.length)];
const bgImage = document.createElement("img");
const bgImageUrl = `Images/${selectedImg}`;
bgImage.classList.add("hidden");
bgImage.src = bgImageUrl;
document.body.style.backgroundImage = `url(${bgImageUrl})`;
document.body.appendChild(bgImage);

✍🏻 폴더 구조!

✍🏻 코드 설명

  • Images폴더 내 파일명과 const bgImages = ["0.jpeg", "1.jpeg", "2.jpeg", "3.jpeg", "4.jpeg", "5.jpeg"]; 을 동일하게 배열을 만들어준다!

  • 랜덤한 이미지를 불러오기에, selectedImg라는 변수를 생성해준다.

  • 이미지가 html구조 내에 없기에 bgImage라는 변수를 생성하여 img태그를 createElement함!

  • Image들의 경로를 저장하는 bgImageUrl라는 변수 생성

  • bgImage.classList.add("hidden"); 여기서 hidden 이라는 클래스를 생성해준 이유는 ?
    document.body.style.backgroundImage = url(${bgImageUrl}); js로 backgroundImage 스타일을 주었기에, 이미지가 2개이기에 원래 이미지는 hidden을 통해 가려줌

  • document.body.appendChild(bgImage); 를 통해 html에 생성!


✍🏻 이미지를 JS로 백그라운드 이미지 처리 하지 않았을 때 모습!


✍🏻 이미지를 JS로 백그라운드 이미지 처리 한 후!

2. 시계 구현

const clock = document.querySelector("h2#clock");

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);

✍🏻 코드 설명

  • 시계를 그려줄 곳을 clock이라는 변수로 선택해줌!
  • 자바스크립트 내장함수인 Date()함수를 통해 getClock()함수를 생성함
  • setInterval(getClock, 1000)은 1초에 한번씩 getClock함수를 호출하는 함수이다.
    이것만 사용해도 되지만, 바로 위 getClock()이라는 함수를 한번 더 호출한 이유는 ?
    💡 setInterval은 1초 뒤 실행되기에, 먼저 getClock()을 호출한것임!

3. username Input으로 받고, 저장하여 인사하기

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);
}

loginForm.addEventListener("submit", onLoginSubmit);

function paintGreetings(username) {
  greeting.innerText = `Hello ${username}`;
  greeting.classList.remove(HIDDEN_CLASSNAME);
}

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
  paintGreetings(savedUsername);
}
 

✍🏻 코드 설명

  • onLoginSubmit()함수에서 event.preventDefault(); 을 사용한 이유는 ?
    💡 submit의 기본 특징이 새로고침하는 특징이 있는데, 이 기본 특징을 막하주는것이 preventDefault()
  • onLoginSubmit()함수에서 loginForm.classList.add(HIDDEN_CLASSNAME); 을 해준 이뉴는?
    💡 로그인을 하였으니까, hidden클래스를 생성해, input이 포함된 form을 가려주기 위함
  • 로그인 하기위해 input으로 받은 값을 username 이라는 변수를 생성하여 저장함
  • 새로고침 시 삭제되지 않도록 localStorage에 (키,밸류) 형태로 저장함
  • paintGreetings()함수에서 greeting.classList.remove(HIDDEN_CLASSNAME);을 한 이유는?
    💡 처음 html에 <h1 id="greeting" class="hidden"></h1> hidden 클래스를 이용하여 h1태그를 생성하였기에, 로그인 전에는 #greeting 부분을 hidden으로 가려주고, 로그인 후에는 hidden이라는 클래스를 remove를 이용하여 지워줌!
  • 마지막, 로그인 여부 확인!
    💡 if (savedUsername === null) 즉, localStorage에 값이 없다면, 로그인을 안한것이기에, loginForm의 hidden클래스를 remove하여 loginForm을 보여준다.
    그리고, 난 후 submit 시, onLoginSubmit을 호출하여 addEventListener을 이용함
    💡else 즉, localStorage에 값이 존재한다면, 로그인을 한것!
    paintGreetings()함수에 localStorage에 저장된 savedUsername을 매개변수로 호출함!

4. 인용문구 랜덤하게 보여주기

const quotes = [
  {
    quote: "신은 용기있는자를 결코 버리지 않는다",
    author: "켄러",
  },
    {
     quote: "어제와 똑같이 살면서 다른 미래를 기대하는 것은 정신병 초기증세다",
    author: "아인슈타인",
  },~~~
 ];
const quote = document.querySelector("#quote span:first-child");
const author = document.querySelector("#quote span:last-child");
const todayQuote = quotes[Math.floor(Math.random() * quotes.length)];

quote.innerText = todayQuote.quote;
author.innerText = todayQuote.author;

💡 코드 설명

  • 10개 정도의 인용구를 배열로 만듦
  • quote 와 author를 각각 변수로 선언
  • random()함수를 활용하여 quotes의 배열의 인덱스를 생성하여 todayQuote라는 변수 선언

5. 🌟 toDoList 생성

const toDoForm = document.getElementById("todo-form");
const toDoInput = document.querySelector("#todo-form input");
const toDoList = document.getElementById("todo-list");
const TODOS_KEY = "todos";
let toDos = [];

function saveToDos() {
  localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}

function deleteToDo(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");
  const button = document.createElement("button");
  button.innerText = "❌";
  button.addEventListener("click", deleteToDo);
  li.appendChild(span);
  li.appendChild(button);
  span.innerText = newTodo.text;
  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);
console.log(savedToDos);

if (savedToDos !== null) {
  const parsedToDos = JSON.parse(savedToDos);
  toDos = parsedToDos;
  parsedToDos.forEach(paintToDo);
}

✍🏻 코드 설명

  • 구현 아이디어는 toDos 라는 빈 배열을 먼저 생성
  • handleToDoSubmit() 함수를 생성 -> input으로 받은 value를 newTodo라는 변수로 선언, 그 이후 toDoInput의 값을 비워주고, 각각의 todo를 오브젝트 형태로 받아줌! 왜?
    💡 delete하려면, 각각의 id값이 필요하기에!!
  • 빈 배열 toDos에 push 해주고, paintToDo()로 화면에 그려줌, SaveToDos()로 localStorage에 저장함. 단, 객체 배열 형태로 저장하기위해 localStorage.setItem(TODOS_KEY, JSON.stringify(toDos)) 이렇게 저장함
  • paintToDo()함수 : "❌" button 클릭시 deleteToDo함수 실행
  • deleteToDo()함수 : filter함수를 통해, toDos라는 배열을 새로 생성해줌, 이후 saveToDos()로 저장해줌
  • 마지막으로, localStorage에 savedToDos 변수를 통해 todo가 있는지 확인하는 작업을 함!
    💡 if (savedToDos !== null) 즉, 값이 있으면, localStorage에 있는 savedToDos를 parse해서 toDos 배열에 넣어준다. 그리고, parsedToDos.forEach(paintToDo); forEach 통해 parsedToDos를 각각 paintToDo()함수를 이용하여 그려준다.

6. 날씨API활용하여 날씨보이기

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`;
  fetch(url)
    .then((response) => response.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("X");
}

navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError);

✍🏻 코드 설명

  • https://openweathermap.org/current#data 사이트에서 로그인 후 API_KEY를 발급받음!
  • lat, lng의 변수 선언 시 position 속성을 통해 위치 저장
  • API를 이용하여 url 변수 선언


👏🏻 왼쪽 : mometun vs 오른쪽 : 직접구현!

profile
프론트엔드 개발자 항상 뭘 하고있는 슬링

0개의 댓글