본문 바로가기

Javscript

[Javascript] 자바스크립트 (1/18) - momentum 클론코딩(only js)

반응형

 

 

오늘은 니꼬쌤의 바닐라 js로 momentum 클론 코딩을 해따

 

 

모멘텀앱이 궁금한 사람은

 

 

https://chrome.google.com/webstore/detail/momentum/laookkfknpbbblfpciffpaejjkokdgca?hl=ko 

 

Momentum

Replace new tab page with a personal dashboard featuring to-do, weather, and inspiration.

chrome.google.com

 

다운 받아 보세욥!

 

 

 

오직 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);
위 코드를 보면 toDoForm을 제출하면 아래의 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();
}
handleToDoSubmit함수는 먼저 새로고침을 방지하기 위해 e.preventDefault()를 실행시켜준다.
안해주면 새로고침이 된다. (이해가 안된다면 없애고 실행시켜봐라!)
toDoInput.Value 즉, Input태그에 적은 값을 newToDo에 저장해주고 초기화 시켜준다.
그리고 toDos에 넣고 paintToDo함수와 saveToDos함수를 실행시킨다.
paintToDo에 newTodoObj(할일과 Date.now())를 주는 이유는 나중에 삭제 버튼을 누르면 그 id를 찾아서 삭제하기 위해서 고유의 id(Date.now())를 주는 것이다.
 
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

 

Current weather data - OpenWeatherMap

Access current weather data for any location on Earth including over 200,000 cities! We collect and process weather data from different sources such as global and local weather models, satellites, radars and a vast network of weather stations. Data is avai

openweathermap.org

이것을 보고 따라하길 바란다. 👊

 

 

 

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;

매우 간단하다 !

 

명언들을 적고 랜덤으로 출력해준다!

 

필자는 저 명언을 너무 좋아하여 두개 밖에 안했지만, 독자들은 여러개 추가해서 테스트해보기를 권한다!

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

원래 알던 것도 있고, 처음 알았던 것도 있었다.

 

리액트에서 잘 이해되지 않던 코드가 오늘 클론 코딩을 하고 난 후에 완벽히는 아니지만 이해가 되는 것을 보고 왜 기본이 중요하다는 지 깨달았다.

 

 

오늘도 열코!!!!!!!!

반응형