Post

Momentum

Momentum

바닐라 JS로 브라우저 크롬의 확장 프로그램인 Momentum을 구현하는 프로젝트

구현내용

  • local storage를 활용한 로그인, to do list 구현과
  • Math.random()을 활용한 랜덤 background 및 명언 출력
  • setInterval을 활용한 시계, openweathermap 을 활용한 날씨 API 등

index.html

헤더에 아이콘 사용을 위해서 폰트어썸 코드를 입력했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!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>Momentum</title>
  <script src="https://kit.fontawesome.com/cdd59ed73b.js" crossorigin="anonymous"></script>
  <link rel="stylesheet" href="./css/style.css">
</head>
<body>
  <div class="wrap">
    <h1 class="hidden">Momentum</h1>
    <article class="container">
      <div class="clock">00:00:00</div>

      <div class="greeting_wrap">
        <form class="form-wrap hidden">
          <input type="text" placeholder="What is your name?" required maxlength="15">
          <button type="submit"><i class="fa-solid fa-unlock-keyhole"></i></button>
        </form>
        <h1 class="greeting hidden"></h1>
      </div>

      <div class="todo_wrap">
        <form class="todo-form">
          <input type="text" placeholder="Write a To Do and Press Enter" required>
        </form>
        <ul class="todo-list"></ul>
      </div>
    </article>

    <div class="quote">
      <span></span>
      <span></span>
    </div>

    <div class="weather">
      <i class="fa-solid"></i>
      <span></span>
      <span></span>
      <p></p>
    </div>
  </div>

  <script src="./js/greeting.js"></script>
  <script src="./js/clocks.js"></script>
  <script src="./js/quote.js"></script>
  <script src="./js/background.js"></script>
  <script src="./js/todo.js"></script>
  <script src="./js/weather.js"></script>
</body>
</html>

js/greeting.js

로그인 전을 위한 .form-wrap 영역과
로그인 후를 위한 .greeting 영역으로 분리되어 있다.

username은 local storage에 저장되어, 페이지 새로고침 시에도 로그인 정보가 유지된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const formWrap = document.querySelector(".form-wrap");
const formWrapInput = formWrap.querySelector("input");
const formWrapButton = formWrap.querySelector("button");

const greeting = document.querySelector(".greeting");
const USERNAME = localStorage.getItem("username");

if(USERNAME == null){   // username이 비어있으면
  formWrap.classList.remove("hidden");
  formWrap.addEventListener("submit", HandlerFormSubmit);
  formWrapInput.value = "";
} else {    // username이 있으면
  PrintGreeting(USERNAME);
}

function HandlerFormSubmit(event){
  event.preventDefault();

  const username = formWrapInput.value;
  localStorage.setItem("username", username);
  formWrap.classList.add("hidden");

  PrintGreeting(username);
}

function PrintGreeting(username){
  greeting.classList.remove("hidden");
  greeting.innerHTML = `Hello <i class="fa-solid fa-face-smile"></i> ${username}!`;
}

js/clock.js

setInterval을 활용하여, 1초 단위로 흘러가는 디지털 시계를 구현했다.
padStart() 함수를 이용해 시계 숫자의 자리수를 맞춰줬다.
padStart()를 사용하기 위해 시,분,초를 문자처리 해줬다. String()

1
2
3
4
5
6
7
8
9
10
11
12
13
const clock = document.querySelector(".clock");

function setClock(){
  const date = new Date();
  const Hours = String(date.getHours()).padStart(2, "0");
  const Menutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");

  clock.innerHTML=`${Hours}:${Menutes}:${seconds}`;
}

setClock();
setInterval(setClock, 1000);

js/quotes.js

Math.Floor()와 Math.random(), array.length를 이용해 원하는 범위 내의 랜덤 숫자를 얻고, 이를 활용해 랜덤으로 글귀를 노출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const quotes = [
  {
    quote: 'I never dreamed about success, I worked for it',
    author: 'Estee Lauder'
  },
  {
    quote: 'Do not try to be original, just try to be good.',
    author: 'Paul Rand'
  },
  {
    quote: 'Do not be afraid to give up the good to go for the great',
    author: 'John D. Rockefeller'
  },
  {
    quote: 'If you cannot fly then run. If you cannot run, then walk. And if you cannot walk, then crawl, but whatever you do, you have to keep moving forward.',
    author: 'Martin Luther King Jr.'
  },
  {
    quote: 'Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time.',
    author: 'Thomas Edison'
  },
  {
    quote: 'The fastest way to change yourself is to hang out with people who are already the way you want to be',
    author: 'REid Hoffman'
  },
  {
    quote: 'Money is like gasoline during a road trip. You do not want to run out of gas on your trip, but you are not doing a tour of gas stations',
    author: 'Tim O Reilly'
  },
  {
    quote: 'Some people dream of success, while other people get up every morning and make it happen',
    author: 'Wayne Huizenga'
  },
  {
    quote: 'The only thing worse than starting something and falling.. is not starting something',
    author: 'SEth Godin'
  },
  {
    quote: 'If you really want to do something, you will find a way. If you do not, you will find an excuse.',
    author: 'Jim Rohn'
  },
];

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

const quoteSelect = quotes[Math.floor(Math.random() * quotes.length)];

quote.innerHTML = quoteSelect.quote;
author.innerHTML = quoteSelect.author;

js/background.js

랜덤으로 배경화면 주소를 얻어온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const BackgroundImages = [
  "0.jpg",
  "1.jpg",
  "2.jpg",
  "3.jpg",
  "4.jpg",
  "5.jpg",
  "6.jpg",
  "7.jpg"
];

const wrap = document.querySelector(".wrap");

const SelectImage = BackgroundImages[Math.floor(Math.random()*BackgroundImages.length)];
wrap.style.backgroundImage = `url(./img/${SelectImage})`;

js/todo.js

local storage와 배열을 활용한 to do list

  • Date.now()함수를 이용해 겹치지 않는 id값과 함께 to do list text를 로컬스토리지에 저장한다.
  • 로컬스토리지에 컨텐츠가 배열 형태로 저장되지 않아 JSON.stringify(toDos) 를 활용해 ‘배열 모양으로’ 저장한다.
  • 로컬스토리지에 배열’모양’의 string 형태로 저장된 컨텐츠를 JSON.parse(savedToDos)를 이용해 실제 배열로 불러와 프린트해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const toDoForm = document.querySelector(".todo-form");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.querySelector(".todo-list");

const TODOS_KEY = "todos";

let toDos = [];

function saveToDos(){
  localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
  // JSON.stringify()를 이용해 배열 문자 형태로 localStorage에 저장
}

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");
  li.appendChild(span);
  span.innerText = newTodo.text;

  const button = document.createElement("button");
  button.type ="button";
  button.innerText = "";
  li.appendChild(button);
  button.addEventListener("click", deleteToDo);

  toDoList.appendChild(li); 
}

function handleToDoSubmit(event){
  event.preventDefault();
  const newTodo = toDoInput.value;
  toDoInput.value = "";

  const newTodoObj = {
    id : Date.now(),
    text : newTodo,
  };

  console.log(toDos.length);
  if(toDos.length < 7){
    toDos.push(newTodoObj);    // toDos Array에 Push
    paintToDo(newTodoObj);
    saveToDos();
  } else {
    alert("You can only enter up to 7.");
  }
}

toDoForm.addEventListener("submit", handleToDoSubmit);


const savedToDos = localStorage.getItem(TODOS_KEY);

if(savedToDos){ // savedToDos가 없으면 null이 반환된다.(false) 있으면 true
  const parsedToDos = JSON.parse(savedToDos); // 문자형태인 savedToDos를 array 형태로 반환
  toDos = parsedToDos;
  parsedToDos.forEach((item) => paintToDo(item));
  // parsedToDos.forEach(paintTodo);
}

js/weather.js

API를 사용하여 간단하게 날씨 정보를 출력할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const weatherIcon = {
  "01": "fa-sun",    // clear sky
  "02": "fa-cloud-sun",  // few clouds
  "03": "fa-cloud",  // scattered clouds
  "04": "fa-cloud-meatball", // broken clouds
  "09": "fa-cloud-rain", // shower rain
  "10": "fa-cloud-showers-heavy", // rain
  "11": "fa-cloud-bolt", // thunderstorm
  "13": "fa-snowflake", // snow
  "50": "fa-water", // mist
};

const API_KEY = "ab25b9205ac4992cdb13d1409a98f3d6";

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`;
  // console.log(url);
  fetch(url).then(response => response.json()).then(data =>{
    // const weatherWrap = document.querySelector(".weather");
    const icon = document.querySelector(".weather i");
    const temp = document.querySelector(".weather span:nth-of-type(1)");
    const weather = document.querySelector(".weather span:nth-of-type(2)");
    const city = document.querySelector(".weather p");

    const iconNum = (data.weather[0].icon).substr(0,2); // icon번호
    const iconEmoji = weatherIcon[iconNum]; // 배열에서 fontawsome class 받아오기
    icon.classList.add(iconEmoji)   // 해당 클래스 +

    temp.innerText = `${Math.floor(data.main.temp)}℃`;
    weather.innerText = `${data.weather[0].main}`;
    city.innerText = data.name;
  });

  // http://openweathermap.org/img/w/10d.png
}

function onGeoError(){
  alert("Can't find you. No weather for you.");
}

navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError);
This post is licensed under CC BY 4.0 by the author.