이 프로젝트는 사실 1년 전 개발한 것이다. 하지만, 이번에 여러 기능을 더 덧붙여 완성하였다. 자바스크립트 입문에 아주 좋은 프로젝트라고 생각한다. 프로젝트에 필수 요소인 CRUD가 포함되어 있기 때문이다. 그리고 자신이 넣고 싶은 기능을 조금씩 넣으면 나만의 투두리스트를 만들 수 있다.
<!DOCTYPE html>
<html lang="en">
<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">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css"
/>
<link rel="stylesheet" href="style.css">
<script src="https://kit.fontawesome.com/d6024b91ee.js" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<title>Todo-list with Quote</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" crossorigin="anonymous"></script>
<div id="todolist">
<div class="main_title">
<h1>
<i class="fas fa-clipboard-list"></i>
Todo-list with Quote
</h1>
</div>
<!--<div class="subtitle">Just write what you have to do!</div>-->
<div class="quote_text"></div>
<div class="quote_author"></div>
<div class="input_section">
<form onsubmit="return false;">
<div class = "buttonininput">
<input type = "text" class="item" autofocus="true">
<button type="button" class="btn-secondary voice_recog"><i class="fa-solid fa-microphone"></i></button>
</div>
<div>
<button type="button" class="input_button"><i class="fa-solid fa-square-plus"></i></button>
</div>
</form>
</div>
<div class="item_list"></div>
</div>
<script src="main.js"></script>
</body>
</html>
head에
<link rel="stylesheet" href="[https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css](https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css)"/>
그리고 쓸 부분에 <i class=””></i>
로 해당 웹사이트에서 원하는 아이콘을 골라 복붙을 하면 된다.
주석 처리는 ‘<!— —>
’
body의 마지막 부분이 가장 좋다.
브라우저 동작 방식에 의해 중간에 위치하면
이러한 문제들이 생길 수 있다.
최하단에 놓을 수 없을 때는,
<script async src="script.js">
<script defer src="script.js">
둘 다 script
태그를 만나도 html parsing이 중단되지 않는다.
<form>
html, body {
caret-color: #8843d6;
background: linear-gradient(90deg, #4a138c, #a977e2);
height: 97%;
/*background: linear-gradient(90deg, #4a138c 33.333333%, #2d2d2d 33.333333%), linear-gradient(90deg, rgb(47, 47, 47) 66.666666%, #4a138c 66.666666%);*/
}
#todolist {
text-align: left;
max-width: 600px;
margin: 20px auto;
background-color: #313131;
height: 100%;
border-radius: 20px;
}
@font-face {
font-family: NanumSquareRoundR;
src: local("NanumSquareRoundR"),
local("NanumSquareRoundR"),
url(NanumSquareRoundR.woff2),
url(NanumSquareRoundR.woff),
url(NanumSquareRoundR.eot),
url(NanumSquareRoundR.ttf);
font-display: swap;
font-weight: bold;
}
.main_title {
margin: 0px;
padding-top: 10px;
padding-bottom: 0px;
text-align: center;
color: white;
font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
font-weight: lighter;
}
.subtitle {
color: white;
text-align: center;
font-style:italic;
font-size:10px;
}
.quote_text {
text-align: center;
font-size: 12px;
color:rgb(255, 255, 255);
font-style: italic;
padding-top: 0px;
margin:0px 20px;
font-weight: 1000;
}
.quote_author {
text-align:right;
font-size: 12px;
margin-right: 6rem;
font-style: italic;
color:rgb(255, 255, 255);
}
.input_section {
margin: 0px 100px 5px 140px;
align-items: center;
display: flex;
}
.input_section form {
align-items: center;
display: flex;
}
.item {
width: 300px;
height: 30px;
border-radius: 15px;
border: 1px solid #fbe7c6;
padding: 0px 30px;
}
*:focus {
outline: none;
}
.input_button {
background-color: transparent;
font-size: 30px;
line-height: 60px;
margin-left: 5px;
color: white;
border: 0;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
ul li {
cursor: pointer;
position: relative;
padding: 12px 8px 12px 100px;
background: #eee;
font-size: 18px;
transition: 0.3s;
margin: 0px 100px;
font-size: 15px;
}
ul li:hover {
background: #ddd;
}
.close {
position: absolute;
right: 10px;
top: 0;
align-items: center;
text-align: center;
margin: 8px 60px;
padding: 4px 8px;
border: none;
background: rgba(255, 255, 255, 0);
}
.close:hover {
background: #4a138c;
border-radius: 100%;
color: white;
}
.cor {
position: absolute;
right: 5px;
top: 0px;
align-items: center;
text-align: center;
margin: 8px 30px;
padding: 8px 8px;
}
.cor:hover {
background: #4a138c;
border-radius: 100%;
color: white;
}
ul li.checked {
background: #ddd;
color: #8b879b;
text-decoration: line-through;
font-size: 15px;
}
ul li.checked::before {
content: "";
position: absolute;
border-color: #272341;
border-style: solid;
border-width: 0 2px 2px 0;
top: 13px;
left: 80px;
transform: rotate(45deg);
height: 15px;
width: 7px;
}
.buttonininput {
position: relative;
}
.item {
border: none;
padding: 0 15px;
height: 40px;
}
.voice_recog {
position: absolute;
top: 15%;
right: 5px;
border-radius: 50%;
background-color: lightgray;
border: none;
width: 30px;
height: 30px;
}
@media screen and (max-width: 768px) {
body {
margin: 0;
}
#todolist {
max-width: 100%;
}
.input_section form {
margin: 0px 30px 5px 30px;
align-items: center;
display: flex;
justify-content: space-between;
}
.item {
width: 200px;
height: 30px;
border-radius: 15px;
border: 1px solid #fbe7c6;
padding: 0px 30px;
}
.close {
position: absolute;
align-items: center;
text-align: center;
margin: 8px 15px;
padding: 4px 8px;
border: none;
background: rgba(255, 255, 255, 0);
}
}
background: linear-gradient(90deg, #4a138c, #a977e2);
html, body {
height: 97%;
}
원하는 height을 주고싶은 element의 부모 요소에 원하는 height을 넣어주면 된다.
*:focus {
outline: none;
}
ul li.checked::before {
content: "";
position: absolute;
border-color: #272341;
border-style: solid;
border-width: 0 2px 2px 0;
top: 13px;
left: 80px;
transform: rotate(45deg);
height: 15px;
width: 7px;
}
@media screen and (max-width: 768px){...}
태블릿 디바이스 (가로 해상도가 768px 보다 작은 화면에 적용)
static
: 기본값, 다른 태그와의 관계에 의해 자동으로 배치되며 위치를 임의로 설정해 줄 수 없습니다.absolute
: 절대 좌표와 함께 위치를 지정해 줄 수 있습니다.relative
: 원래 있던 위치를 기준으로 좌표를 지정합니다.fixed
: 스크롤과 상관없이 항상 문서 최 좌측상단을 기준으로 좌표를 고정합니다.inherit
: 부모 태그의 속성값을 상속받습니다.relative
인 컨테이너 내부에 absolute
인 객체가 있으면 절대 좌표를 계산할 때, relative
컨테이너를 기준점으로 잡게 됩니다. (없다면 전체 문서가 기준)
@font-face {
font-family: NanumSquareRoundR;
src: local("NanumSquareRoundR"),
local("NanumSquareRoundR"),
url(NanumSquareRoundR.woff2),
url(NanumSquareRoundR.woff),
url(NanumSquareRoundR.eot),
url(NanumSquareRoundR.ttf);
font-display: swap;
font-weight: bold;
}
브라우저 렌더링을 보고, 폰트 용량을 줄이고자 했다. 하지만 나의 경우에는 다른 폰트라서 적용이 안되는 듯하다.
'use strict';
window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
var itemList = [];
var inputButton = document.querySelector(".input_button");
inputButton.addEventListener("click", addItem);
document.querySelector(".item").addEventListener('keypress',function (e){
if(e.key === 'Enter'){
addItem();
}
})
function addItem() {
var item = document.querySelector(".item").value;
if (item != "") {
itemList.push(item);
document.querySelector(".item").value = "";
document.querySelector(".item").focus();
}
showList();
}
function showList() {
var list = "<ul>"
for (var i = 0; i <itemList.length; i++) {
list += "<li>" + itemList[i] + "<span class='close' id=" + i + ">" + "\u00D7" + "</span><i class='fa-solid fa-pen cor' id=" + i + ">" + "</i></li>";
}
list += "</ul>";
document.querySelector(".item_list").innerHTML = list;
var devareButtons = document.querySelectorAll(".close");
var corButtons = document.querySelectorAll(".cor");
for (var i = 0; i < devareButtons.length; i++) {
devareButtons[i].addEventListener("click", devareItem);
corButtons[i].addEventListener("click", correctItem);
}
}
function devareItem() {
var id = this.getAttribute("id");
itemList.splice(id, 1);
showList();
}
function correctItem() {
let text = prompt("수정할 내용을 입력하세요.");
if (text == null || text == "") {
return;
}
var id = this.getAttribute("id");
itemList[id] = text;
showList();
}
var checkList = document.querySelector('.item_list');
checkList.addEventListener('click', event => {
if (event.target.tagName === 'LI') {
event.target.classList.toggle('checked');
}
});
var quoteText = document.querySelector(".quote_text")
var quoteAuthor = document.querySelector(".quote_author")
function getQuote(){
function randomItem(a){
return a[Math.floor(Math.random()*a.length)];
}
fetch("https://type.fit/api/quotes")
.then(function(response) {
return response.json();
})
.then(function(data) {
var random = randomItem(data);
var author = random.author;
var text = random.text;
quoteText.innerText = `${text}`;
quoteAuthor.innerText = `- ${author} -`;
});
}
getQuote();
document.querySelector(".quote_text").innerHTML = quoteText.innerText;
document.querySelector(".quote_author").innerHTML = quoteAuthor.innerText;
function init(){
getQuote();
}
let recognition = new SpeechRecognition();
recognition.interimResults = true; // 중간 결과를 반환할지 여부
recognition.lang = 'ko-KR';
recognition.onresult = function(event) {
console.log(event.results)
var text = event.results[0][0].transcript;
console.log(text);
document.querySelector(".item").value = text;
document.querySelector(".item").focus();
}
recognition.onspeechend = () => {
recognition.stop();
}
document.querySelector(".voice_recog").addEventListener('click', function(){
recognition.start();
});
'use strict';
strict mode는 자바스크립트 언어의 문법을 좀 더 엄격히 적용하여 오류를 발생시킬 가능성이 높거나 자바스크립트 엔진의 최적화 작업에 문제를 일으킬 수 있는 코드에 대해 명시적인 에러를 발생시킨다. (콘솔에 에러를 확인할 수 있다.)
이벤트를 등록하는 두가지 방식
const $button = document.querySelector('button');
$button.onclick = function(){
console.log('button click');
};
const $button = document.querySelector('button');
$button.addEventListner('click',function(){
console.log('button click');
});
function addItem() {
var item = document.querySelector(".item").value;
if (item != null) {
itemList.push(item);
document.querySelector(".item").value = "";
document.querySelector(".item").focus();
}
showList();
}
할 일을 추가해주는 함수
document.querySelector(".item").value = "";
➡️ 입력창 비워줌
document.querySelector(".item").focus();
➡️ 커서 깜빡임
function showList() {
var list = "<ul>"
for (var i = 0; i <itemList.length; i++) {
list += "<li>" + itemList[i] + "<span class='close' id=" + i + ">" + "\u00D7" + "</span></li>";
}
list += "</ul>";
document.querySelector(".item_list").innerHTML = list;
var devareButtons = document.querySelectorAll(".close");
for (var i = 0; i < devareButtons.length; i++) {
devareButtons[i].addEventListener("click", devareItem);
}
}
할 일 리스트를 보여주는 함수
"\u00D7"
➡️ ‘x’버튼 클래스는 close
document.querySelector(".item_list").innerHTML = list;
➡️ class가 item_list인 곳에 HTML 형식으로 넣어줌. 즉 list는 HTML형식이다.
devareButtons[i].addEventListener("click", devareItem);
➡️ 각 close 버튼에 이벤트를 넣어준다.
function devareItem() {
var id = this.getAttribute("id");
itemList.splice(id, 1);
showList();
}
해당 할 일을 삭제하는 함수
itemList.splice(id, 1);
➡️ id번 인덱스에서 1개 제거
var quoteText = document.querySelector(".quote_text")
var quoteAuthor = document.querySelector(".quote_author")
function getQuote(){
function randomItem(a){
return a[Math.floor(Math.random()*a.length)];
}
fetch("https://type.fit/api/quotes")
.then(function(response) {
return response.json();
})
.then(function(data) {
var random = randomItem(data);
var author = random.author;
var text = random.text;
quoteText.innerText = `${text}`;
quoteAuthor.innerText = `- ${author} -`;
});
}
getQuote();
document.querySelector(".quote_text").innerHTML = quoteText.innerText;
document.querySelector(".quote_author").innerHTML = quoteAuthor.innerText;
random으로 명언을 뽑는 함수
fetch란 무엇인가?
promise 형태로 리턴하며 비동기적 동작 가능성이 높다.
성공적으로 실행되면 response 객체를 리턴한다.
성공시 then(function(response){...})
실패시 catch(function(reason){...})
안에 function은 콜백함수이다.
function correctItem() {
let text = prompt("수정할 내용을 입력하세요.");
if (text == null || text == "") {
return;
}
var id = this.getAttribute("id");
itemList[id] = text;
showList();
}
해당 할 일을 수정해주는 함수
입력값이 없으면 그대로 유지하도록 하였다.
let recognition = new SpeechRecognition();
recognition.interimResults = true; // 중간 결과를 반환할지 여부
recognition.lang = 'ko-KR';
recognition.onresult = function(event) {
console.log(event.results)
var text = event.results[0][0].transcript;
console.log(text);
document.querySelector(".item").value = text;
document.querySelector(".item").focus();
}
recognition.onspeechend = () => {
recognition.stop();
}
document.querySelector(".voice_recog").addEventListener('click', function(){
recognition.start();
});
MDN 웹 문서에서 Web Speech API의 정보를 확인 할 수 있다.
SpeechRecognition 객체를 사용하여 음성 인식 기능을 구현하였다. SpeechRecognition에는 여러 프로퍼티, 메서드, 이벤트 핸들러가 있다.
Property
SpeechRecognition.lang = 'ko-KR' : 언어를 한국어로 설정
SpeechRecognition.continuous : 인식된 음성을 쭉 이어서 받을지(true), 하나씩만 받을지(false: default) 결정
SpeechRecognition.iterimResults: 실시간으로 음성 인식한 결과를 화면에 표시할지(true), 다 끝나면 완료된 결과만 보이게 할지(false)
Method
SpeechRecognition.abort(): 현재 들어오는 음성 데이터를 듣고 있는 speech recognition 서비스를 멈춘다. SpeechRecognitionResult 객체를 리턴하지 않는다.
SpeechRecognition.start(): 음성 인식 시작
SpeechRecognition.stop(): 음성 인식 멈춤. SpeechRecognitionResult 객체 리턴.
Event Handler
onstart: 음성 데이터를 듣기 시작할 때 실행되는 이벤트 핸들러
onend: 음성 인식이 끊기면 실행
onerror: 에러 발생시 실행
onresult: 음성 인식 서비스가 result를 리턴할 때 실행
그렇다면 event.results
는 무엇일까? 이는 여러개의 SpeechRecognitionResult 객체를 가지고 있는 SpeechRecognitionResultList 라는 객체이다. 이 리스트에 result 객체를 계속 쌓아둔 것이다.
더 나아가, 왜 event.result[0][0]
일까? SpeechRecognitionResult 객체는 여러개의 SpeechRecognitionAlternative 객체로 이뤄져있기 때문에 event.result[0][0] 2차원으로 접근해야 SpeechRecognitionAlternative.transcript 으로 인식된 단어의 string 값을 리턴받을 수 있다.
netlify에서 github를 연동하여 쉽게 배포할 수 있다.
https://velog.io/@janeljs/making-a-todolist
https://2donny-world.tistory.com/10
https://www.youtube.com/playlist?list=PLuHgQVnccGMBVQ4ZcIRmcOeu8uktUAbxI