최근 코딩 자율학습단 5기를 끝마침과 더불어 직접 무언가를 만들어보고 싶지만 아직 초짜라는 것을 잘 알기에 간단한 것부터 차근차근 해보고싶어 만들어보았다.
CSS 적용 시 편의성을 위하여 방법론 중 하나인 컨테이너와 콘텐츠의 분리(OOCSS)를 최대한 생각
하며 작성하고자 했다.
<!-- 레이아웃 -->
<div class="main-container">
<div class="main-wrapper">
<!-- 기본적으로 여러개의 div 클래스를 부모자식 관계로 선언, 콘텐츠와 레이아웃을 분리한다. -->
/* 기본 CSS */
*{
padding:0;
margin:0;
box-sizing: border-box;
}
body{
margin : 0 auto;
width : 100%;
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
background-size: cover;
color : white;
}
1. 시계
Text 부분을 단순히 00:00으로 채우고, 이를 JS의 Date 객체와 setInterval 함수를 이용하여 시간을 초마다 업데이트되게 만들어 시계를 구현
<!-- 시계 -->
<h2 id = clock>00:00</h2>
.main-wrapper #clock{
font-size:6rem;
}
querySelector를 이용해 clock태그를 clock 변수로 가져오고, getClock 함수를 선언하여 Date
객체를 받아와 각 변수에 시, 분, 초를 할당 후 태그의 Text 부분에 setInterval 함수로 1초마다
업데이트 되도록 설정
const clock = document.querySelector("h2#clock"); // html 요소 가져오기
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); // 초마다 반복
2. 유저 인식
form 태그의 input 태그를 이용하여 유저의 이름을 입력받고, submit으로 전송하여 localStorage에 저장하는 형식으로 구현
<form id="login-form" class = "hidden">
<input required
maxlength="15"
type="text"
placeholder="What is your name?">
<input type="submit" value="Log In" id = "log-in">
</form>
<h1 id = greeting class="hidden"></h1> <!-- 유저 입력 후에 입력창이 사라지고 활성화 -->
.main-wrapper #login-form input{
background-color : rgba(256,256,256,0.2);
width: 300px;
text-align: center;
height:40px;
border-radius: 20px;
border-color: rgba(0,0,0,0.1);
border: none;
}
.main-wrapper #login-form input::placeholder{
font-size: 1rem;
color: black;
}
.main-wrapper #login-form input:focus{
outline: none;
box-shadow: rgba(256,256,256,0.5);
}
.main-wrapper #login-form input#log-in{
display: none;
}
const로 변수를 선언하여 html 태그를 불러오고, 자주 쓰는 string 요소 또한 변수 할당
addEventListener를 통해 활성화되는 요소마다 함수를 실행하여 동작할 수 있도록 구현
const loginForm = document.querySelector("#login-form");
const loginInput = document.querySelector("#login-form input");
const greeting = document.querySelector("#greeting");
const removeUser = document.querySelector("#remove-user");
const HIDDEN_CLASSNAME = "hidden";
const USERNAME_KEY = "username";
// submit 버튼을 click 시 loginform에 hidden 클래스가 추가되어 사라지고, localStorage에
// loginInput.value를 저장하도록 작성
function onLoginSubmit(event){
event.preventDefault();
loginForm.classList.add(HIDDEN_CLASSNAME);
localStorage.setItem(USERNAME_KEY, loginInput.value);
paintGreetings();
}
// localStorage에서 불러온 유저 이름을 변수에 할당하고, 시간별 인사를 함수로 구현하여 표현
function paintGreetings() {
const username = localStorage.getItem(USERNAME_KEY);
SayHifunc(username);
greeting.classList.remove(HIDDEN_CLASSNAME);
}
// 시간별로 인사를 나누어 각 시간별 인사와 유저 이름을 출력하도록 설정
function SayHifunc(username){
const currentHours = new Date().getHours();
console.log(typeof currentHours);
if(currentHours >= 6 && currentHours < 12 ){
greeting.innerText = `Good Morning! ${username}.`;
}
else if(currentHours >= 12 && currentHours < 18){
greeting.innerText = `Good Afternoon! ${username}.`;
}else if(currentHours >= 18 && currentHours < 21){
greeting.innerText = `Good Evening! ${username}.`;
}else{
greeting.innerText = `Good Night! ${username}.`;
}
}
const savedUserName = localStorage.getItem(USERNAME_KEY);
// 만약 localStorage에 유저 이름이 없을 경우, loginForm을 보여지게 설정, 이름이 있을 시
// 그대로 인사 출력
if(savedUserName === null){
loginForm.classList.remove(HIDDEN_CLASSNAME);
loginForm.addEventListener("submit", onLoginSubmit);
}else{
paintGreetings();
}
3. TODO 리스트
유저가 작성한 할일 리스트를 출력하도록 구현하고, 새로고침 하더라도 계속 유지되도록 이를 localStorage에 배열 형식으로 저장, 더불어 delete 버튼도 추가하여 삭제도 가능하도록 구현
<div class = "todo-container">
<form id="todo-form">
<input type="text" placeholder="Write a To Do and Press Enter" required>
</form>
<div class="todo-list">
<ul id="todo-list"></ul>
</div>
</div>
.main-wrapper .todo-container{
justify-content: center;
align-items: center;
position:fixed;
width:300px;
top: 50px;
right:20px;
}
.todo-container .todo-list{
margin-top: 20px;
color:white;
}
.todo-container button{
margin-left:5px;
background-color: inherit;
border:none;
}
.main-wrapper #todo-form input:focus{
outline: none;
box-shadow: rgba(256,256,256,0.5);
}
.main-wrapper #todo-form input{
width: 250px;
text-align: center;
height:40px;
border-radius: 20px;
border-color: rgba(0,0,0,0.1);
border: none;
background-color: rgba(256,256,256,0.2);
}
.main-wrapper #todo-form input::placeholder{
font-size:15px;
color:black;
}
localStrorage를 적극 활용하여 TODO 리스트의 출력, 저장, 삭제를 구현
const todoForm = document.getElementById("todo-form");
const todoList = document.getElementById("todo-list");
const todoInput = document.querySelector("#todo-form input");
const TODOS_KEY = "todos";
let toDos = [];
// toDos 배열을 선언하여 리스트 작성 시마다 추가하고, 이를 localStorage에 JSON.stringify() // 를 이용해 배열 형식으로 저장하고 꺼내쓸 수 있도록 작성
function saveToDos(){
localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}
// 발생 event를 인자로 받아와 target의 부모 노드, 즉 TODO 리스트를 선택하여 삭제 가능하도록
// 구현
function deleteTodo(event){
const li = event.target.parentNode;
li.remove();
toDos = toDos.filter((toDo) => toDo.id !== parseInt(li.id));
saveToDos();
}
// ul태그만 작성되어 있는 시점에서 TODO 리스트 작성시마다 li태그가 생성되도록 구현
function paintTodo(newTodo) {
const li = document.createElement("li");
li.style.listStyle = `none`;
li.id = newTodo.id;
const span = document.createElement("span");
span.innerText = newTodo.text;
const button = document.createElement("button");
button.innerText = "✅";
button.addEventListener("click", deleteTodo);
li.appendChild(span);
li.appendChild(button);
todoList.appendChild(li);
li.style.marginTop = `10px`;
}
// TODO리스트를 작성하고 전송할때마다 toDos 배열에 추가되도록 구현
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);
// localStorage에 TODO 리스트가 존재할 경우, 리스트 항목을 추가할 때마다 업데이트되도록 구현
if(savedToDos !== null){
const parsedToDos = JSON.parse(savedToDos);
toDos = parsedToDos;
parsedToDos.forEach(paintTodo);
}
4. 명언
quoteList 라는 배열을 선언하여 명언을 넣어준 후, random() 함수를 이용해 인덱스를 무작위로 설정되도록 구현하여 명언이 랜덤하게 나오도록 구현
<div id="quote">
<span></span>
<span></span>
</div>
.main-wrapper #quote{
position:fixed;
bottom:0;
left: 50%;
transform: translateX(-50%);
max-width : 800px;
padding : 0 10px;
margin-bottom : 20px;
text-align: center;
}
.main-wrapper #quote span{
display:block;
color:beige;
}
.main-wrapper div#quote span:nth-child(1){
font-size: 20px;
}
const quoteList = [] // 생략
const quote = document.querySelector("#quote span:first-child");
const author = document.querySelector("#quote span:last-child");
function setQuote(){
const todayQuote = quoteList[Math.floor(Math.random() * quoteList.length)];
quote.innerText = todayQuote.quote;
if(todayQuote.author !== ""){
author.innerText = `- ${todayQuote.author} -`;
}
}
function UpdateQuote(){
setQuote();
setInterval(setQuote, 5000);
}
UpdateQuote();
5. 날씨
API와 fetch()를 이용해 실시간으로 변경되는 날씨 정보를 전달받고 이를 태그를 통해 출력
<div id="weather">
<img>
<span></span>
<span></span>
</div>
.main-wrapper #weather{
background-color: rgba(256,256,256,0.1);
justify-content: center;
align-items: center;
position:fixed;
width:250px;
height:330px;
bottom: 30px;
right:43px;
border:solid 5px rgba(256,256,256,0.3);
border-radius: 10px;
}
.main-wrapper #weather span{
display: block;
margin-top:10px;
}
.main-wrapper #weather img{
border:none;
width: 160px;
height:160px;
}
.main-wrapper #weather span:nth-child(2){
margin-top:10px;
font-size:1.8rem;
}
.main-wrapper #weather span:last-child{
font-size:1.5rem;
}
navigator를 이용해 내 위치정보(위도 & 경도)를 변수에 할당하고, 백틱과 API를 활용해 url을 작성한 후 변수 할당, fetch 함수를 이용해 날씨 정보를 받아옴과 더불어 openweathermap에서 제공하는 이모지를 활용해 변하는 날씨마다 img가 바뀌도록 구현
const API_KEY = "0e41118d3ed276d850c79fd62e832a9f"; // API 키
// getCurrentPosition의 인자 중 하나, 속도나 정확도 등을 설정
var options = {
enableHighAccuracy: false,
timeout: 5000,
maximumAge: 0,
};
// 위도와 경도를 할당하고 이를 url에 할당하여 API를 통한 날씨 정보를 받아옴
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}&units=metric`;
fetch(url)
.then((response) => response.json())
.then((data) => {
const iconUrl = `https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png`
const weather = document.querySelector("#weather img:nth-child(1)");
const city = document.querySelector("#weather span:nth-child(2)");
const degree = document.querySelector("#weather span:last-child");
city.innerText = data.name;
weather.src = iconUrl;
degree.innerText = `${data.main.temp}`;
});
}
// 위치 정보를 받아오지 못할 경우
function onGeoError(){
alert("Can't find you. No weather for you.");
}
navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError, options)
마무리
간단한 것이지만 스타트라인에서 시도한 나쁘지 않은 작업이었다고 생각한다.
더욱 나중에 다시 이 글을 봤을 때 이때 왜 이거밖에 못했지… 라고 생각할 수 있도록 정진하자~
'Front-End Study > 나만의 프로젝트' 카테고리의 다른 글
우주배경 미니멀 로그인 Form 만들기 (0) | 2024.02.07 |
---|---|
Google 시작 홈페이지 만들어보기 (0) | 2024.01.15 |
메이플스토리 홈페이지 로그인 화면 만들어보기 (2) | 2024.01.09 |