📜 사실 노션에서 쓰고 옮긴거라 노션이 더 가독성 좋아요.
https://yopark.notion.site/JS-TIL-54b9cc2355c14c58b5dfd05cd18da388
바닐라 JS로 크롬 앱 만들기 - 노마드 코더 Nomad Coders
💡 기초부터 설명하는 글이 아니라, 필자가 새롭게 배운 내용을 주관적으로 정리한 글입니다.위 강의는 Chrome 확장 프로그램인 Momentum을 클론코딩하는 방식으로 진행된다.
위 링크에 들어가면, 필자의 토이 프로젝트 결과물을 볼 수 있다.
clock
: 현재 시각greeting
: username을 받아 localStorage
에 저장하여 인사말 표시background
, quote
(위 사진에서 중앙 하단) : 랜덤 배경, 랜덤 명언search
(좌측 상단) : 구글에 검색하기weather
(우측 상단) : OpenWeatherMap API를 이용하여 현재 지역, 날씨, 기온 표시today-focus
: 오늘의 가장 큰 TODO 목표를 적어두는 공간pomodoro-timer
: 시계 아이콘을 누르면 작동하며, 25분(1뽀모도로) 타이머다.TODO
(우측 하단) : localStorage
에 저장되어 새로고침해도 초기화되지 않음.<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/style.css">
<title>Momentum App</title>
</head>
<body>
<header>
<form class="search-form">
<input type="text" placeholder="Search" required>
</form>
</header>
<section>
<div class="clock">00:00</div>
<form class="login-form hidden">
<input type="text" placeholder="What is your name?" required maxlength="15">
</form>
</section>
<footer>
<label class="todo-label hidden" for="hiddenToDoBtn">Todo</label>
<input type="checkbox" class="hidden" id="hiddenToDoBtn">
<div class="todo hidden">
<ul class="todo__list"></ul>
<form class="todo__form hidden">
<input type="text" placeholder="Write a To Do" required>
</form>
</div>
</footer>
</body>
</html>
html
, head
, body
의 하위 태그에는 들여쓰기를 하지 않아야 가독성에 좋음.Avoid unnecessary
id
attributes.
Preferclass
attributes for styling anddata
attributes for scripting.
id
는 꼭 필요한 상황이 아니라면, 쓰지 마라.
id
는 이건 정말 단 하나밖에 없다. 앞으로도 절대 안 생긴다. 이런 확신이 있을때 씁니다. 만약 이후에 디자인이나 UX가 바뀌면서 뭔가 비슷한 형태가 더 생길 가능성이 있다면, 단 하나의 요소에도class
를 줍니다. (위 링크, 신승우님의 답변)
<header>
<form class="search-form">
<input type="text" placeholder="Search" required>
</form>
<div class="weather">
<div class="weather__container">
<img>
<span class="weather__temp"></span>
</div>
<div class="weather__city"></div>
</div>
</header>
weather__container__temp
처럼 적진 않는다.header
, nav
, footer
를 제외한, 글의 지배적인 영역을 포괄하는 것 : main
label
태그의 for
속성<label class="todo-label hidden" for="hiddenToDoBtn">Todo</label>
<input type="checkbox" class="hidden" id="hiddenToDoBtn">
라벨로 checkbox
역할을 대신하고 싶을 때 쓰는 방법.
HTML에 JavaScript를 추가하는 최고의 방법!
<head>
</head>
<body>
<script src="js/todo.js"></script>
</body>
<head>
<script defer src="js/todo.js"></script>
</head>
<body>
</body>
위 방법을 아래 방법으로 고쳐 쓰세요!
defer
를 왜 써야 하는가?body
끝에 script
를 넣으면, HTML을 다 받은 뒤에 JS를 받기 시작하기 때문에, head
끝에 넣는 방식이 선호됨.head
끝에 넣는 것 중에 async
방식이라고도 있는데, HTML 파싱 중이라도 JS 다운로드가 완료되면 파싱 중인 HTML을 Block하고 바로 실행해버리기 때문에, defer
를 사용한다.defer
는 위 두 문제를 모두 해결한다. JS를 받는 건 HTML 파싱 중에 진행되지만, 다 다운로드 되었더라도 실행을 지연시킨다.
body, html {
margin: 0;
height: 100%;
color: white;
}
body,
html {
margin: 0;
height: 100%;
color: white;
}
위를 아래로 바꿔라.
hidden
class.hidden {
display: none;
}
이렇게 설정해두고,
const HIDDEN_CLASSNAME = "hidden";
loginForm.classList.add(HIDDEN_CLASSNAME);
toDoForm.classList.remove(HIDDEN_CLASSNAME);
이런 식으로 숨기거나, 다시 보이게 하는 것이 클리셰.
div p
와 div > p
의 차이점div p
: div
의 하위에 있는 모든 p
(하위 선택자)div > p
: div
의 바로 하위에 있는 p
만 해당. (자식 선택자)E + F
: 같은 부모를 가지고, E 바로 뒤에 오는 F에만 적용. (인접 형태 선택자)E ~ F
: 같은 부모를 가지고, E 뒤에 오는 F에 적용. (일반 형태 선택자)#hiddenToDoBtn:checked + .todo {
display: block;
position: absolute;
bottom: 80px;
right: 20px;
}
위의 선택자의 뜻은, hiddenToDoBtn이 체크되었을 경우에, 바로 뒤에 오는 todo
class를 가진 요소를 display: none
에서 display: block
으로 바꾼다는 의미다.
코드는 다음과 같다.
<label class="todo-label hidden" for="hiddenToDoBtn">Todo</label>
<input type="checkbox" class="hidden" id="hiddenToDoBtn">
<div class="todo hidden">
<ul class="todo__list"></ul>
<form class="todo__form hidden">
<input type="text" placeholder="Write a To Do" required>
</form>
</div>
label for
을 이용하고, checkbox
를 숨겨서 label이 checkbox의 역할을 대신하게 함.
#hiddenToDoBtn:checked + .todo {
display: block;
position: absolute;
bottom: 80px;
right: 20px;
}
.todo__list {
list-style-type: none;
padding-left: 10px;
display: flex;
flex-direction: column;
align-items: left;
}
.todo
는 default 값이 hidden
이므로, display: none
이다.
하지만 :checked
되었을 때, display: block
으로 바뀌면서 모달 창이 나타나게 된다.
padding
과 margin
혼동하지 않도록 주의!
top
→right
→bottom
→left
)ex) padding: 10px 0 20px 0
⇒ padding-top: 10px; padding-bottom: 20px;
display: flex
속성 정리flex-direction
: 요소의 방향row
, column
row-reverse
, column-reverse
justify-content
: (가로 기준) 가로 정렬 방향flex-start
, center
, flex-end
space-between
, space-around
align-items
: (가로 기준) 세로 정렬 방향flex-start
, center
, flex-end
baseline
, stretch
position
요소는 top, left, bottom, right 속성을 이용하여 위치될 수 있다. 그러나
position: static (기본값)
일 때는 작동하지 않는다.
position: absoulte
기준으로 설명하겠다.
ex) top: 0
⇒ 요소가 창 상단에 딱 붙어있다.
ex) bottom: 20px; right: 0
⇒ 요소가 우측 하단에 있지만, 아래로부터는 20px 띄워져 있다.
이런 방식으로 positioning이 가능하다.
static
: 기본값absolute
: 일반적으로 창 좌측상단이 기준position: static
을 제외한 position 설정이 되어 있다면, 그 부모가 기준이 된다. 예외적인 상황이라 잘 쓰진 않음.relative
: 원래 static
때 자신이 있어야 할 자리가 기준fixed
: 처음에는 absolute
처럼 배치되는데, 스크롤을 움직여도 그 자리에서 변하지 않음."use strict";
var
의 문제점var n = 1;
var n = 2; // 이게 됨
console.log(n) // 1
var n = 1;
→ var
대신 let
, 상수에는 되도록 const
를 써라.
document.querySelector
를 애용하자.getElementById
도 상관없긴 한데, CSS랑 통일돼서 보기 좋음. 하지만 Element 클래스에는 querySelector
는 있어도 getElementById
는 없다는 사실을 기억하자.
Document 클래스와 Element 클래스가 다르다는 사실을 기억하자.
상속 관계다. DOM 트리가 아니다. 혼동하지 말자.
const toDoForm = document.querySelector(".todo__form");
이라고 했다면, 앞의 document
는 Document 클래스이지만 반환값인 toDoForm
은 Element 클래스이다.toDoForm.getElementById
는 오류다.getElementsByClassName
, getElementsByTagName
은 제공한다.==
과 ===
의 차이==
은 JS에서 ===
이다.그럼 ==
은 뭐냐?
console.log(1 == "1"); // true
console.log(null == undefined); // true
console.log(true == 1); // true
console.log(0 == ""); // true
이런 느낌이다.
도대체 ==
기능을 쓸 일이 있을지 모르겠다.
HTMLElement
에서 사용되는 프로퍼티Element.classList
add
, remove
EventTarget.addEventListener
HTMLElement.innerText
HTMLElement.style
display
, textDecoration
등 모든 CSS 속성에 접근 가능.HTMLInputElement.value
localStorage.setItem(FOCUS_KEY, JSON.stringify(focusObj));
const savedFocus = localStorage.getItem(FOCUS_KEY);
if (savedFocus !== null) {
const focusObj = JSON.parse(savedFocus);
paintFocus(focusObj);
}
let time = 25*60; // 기본 25분
let timerFunc; // 전역변수로 선언해야 clearInterval이 되네... 왜지
function togglePomodoroTimerBtn() {
const h2 = document.querySelector(".pomodoro > h2");
if (pomodoroTimerBtn.value === "NO") {
pomodoroTimerBtn.value = "YES";
pomodoroTimerBtn.innerText = "Stop";
h2.innerText = "Focus";
timerFunc = setInterval(() => {
if (time === 0) {
clearInterval(timerFunc);
return;
}
time--;
const min = String(Math.floor(time/60)).padStart(2, "0");
const sec = String(time%60).padStart(2, "0");
pomodoroTimer.innerText = `${min}:${sec}`;
}, 1000);
} else {
pomodoroTimerBtn.value = "NO";
pomodoroTimerBtn.innerText = "Start";
h2.innerText = "Time to focus.";
clearInterval(timerFunc);
}
}
String.padStart
fetch(url)
function onGeoOk(position) {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${API_KEY}&lang=kr&units=metric`
fetch(url)
.then(response => response.json())
.then(data => {
const iconUrl = `https://openweathermap.org/img/w/${data.weather[0].icon}.png`;
const weatherImage = document.querySelector(".weather__container > img");
const temp = document.querySelector(".weather__temp");
const city = document.querySelector(".weather__city");
weatherImage.src = iconUrl;
temp.innerText = `${Math.floor(data.main.temp*10)/10}°`;
city.innerText = `${data.name}`;
});
}
function onGeoError() {
alert("Can't find you. No weather for you.")
}
navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError);
Math.round
는 반드시 소수 첫째자리에서 반올림하는 기능만 제공한다.Math.floor(n*10)/10)
이라는 말도 안되는 방법을 사용해야 한다…FAQ › 바닐라 JS - 노마드 코더 Nomad Coders
위 링크를 들어가보면, 같은 기능이지만 얼마나 다양한 디자인이 나올 수 있는지 감탄할 수 있다.