A simple, lightweight web todo list built with Vanilla JavaScript
순수 자바스크립트로 만든 가볍고 심플한 웹 투두 리스트
| Feature | Description (EN) | 설명 (KR) |
|---|---|---|
| Add | Add tasks via input box | 입력창으로 task 추가 |
| Complete | Toggle completion with checkbox | 체크박스로 완료 처리 |
| Edit | Click text to edit inline | 텍스트 클릭으로 인라인 수정 |
| Delete | Hover to reveal delete button | 마우스 호버 시 삭제 버튼 표시 |
| Persist | Auto-saved to LocalStorage | LocalStorage 자동 저장 |
Enter text in the input box and press Enter or click the Add button to add a task.
입력창에 텍스트를 입력하고 Enter 키를 누르거나 Add 버튼을 클릭하면 task 추가.
Click the checkbox to mark a task as complete. A strikethrough and gray style will be applied.
체크박스 클릭 시 task 완료 처리. 취소선과 회색 스타일 적용됨.
Click on a task text to turn it into an editable input box. Edit the content, then press Enter or click outside the input to save.
텍스트 클릭 시 입력창으로 변경. 내용 수정 후 Enter 키 또는 입력창 외부 클릭으로 저장.
Hover over a task to reveal the delete button (X). Click it to remove the task.
task에 마우스 호버 시 삭제 버튼(X) 표시. 클릭하면 task 삭제.
모든 할 일은 todos 배열에 객체 형태로 저장. LocalStorage에는 이 배열이 JSON 문자열로 직렬화되어 저장됨.
todos = [
{ text: "할 일 내용", done: false },
{ text: "완료된 할 일", done: true }
]| Key | Type | Description |
|---|---|---|
text |
string |
할 일 텍스트 |
done |
boolean |
완료 여부 (체크박스 상태) |
addTodo() 함수가 입력값을 받아 배열에 push 후 재렌더링.
function addTodo() {
let todo = todoInput.value.trim();
if (todo === "") return; // 공백 입력 차단
todos.push({text: todo, done: false});
todoInput.value = ""; // 입력창 초기화
localStorage.setItem("todo", JSON.stringify(todos));
todoDisplay();
}핵심 포인트
trim()으로 앞뒤 공백 제거 후 검증 → 공백만 입력하는 경우 차단- 새 항목은 항상
done: false로 시작 - 상태 변경 → LocalStorage 저장 → 화면 갱신 순서를 모든 변경 함수가 동일하게 따름
todoDisplay() 함수가 todos 배열 전체를 순회하며 HTML 문자열을 생성, todo-list 영역에 innerHTML로 일괄 주입.
function todoDisplay() {
if (todos.length === 0) {
todoList.innerHTML = `<p>할 일이 없습니다. 할 일을 추가해보세요.</p>`;
return;
}
let html = "";
for (let i = 0; i < todos.length; i++) {
html += `
<div id="todo-item-${i}" class="${todos[i].done ? "done" : ""}">
<input type="checkbox" ${todos[i].done ? "checked" : ""} onclick="toggleDone(${i})">
<span id="todo-text-${i}" onclick="editTodo(${i})">${escapeHtml(todos[i].text)}</span>
<button class="deleteBtn" onclick="deleteTodo(${i})">X</button>
</div>
`;
}
todoList.innerHTML = html;
}핵심 포인트
- 전체 재렌더링 방식: 변경이 발생할 때마다 리스트 전체를 새로 렌더링. 인덱스 기반 접근이 항상 일관되게 동작
- 빈 상태 처리: 배열이 비어 있을 때 안내 메시지 표시
- 조건부 클래스:
done상태에 따라class="done"부여 → CSS에서 취소선 처리 - 사용자 입력은
escapeHtml()통과 — 자세한 내용은 Safe Rendering 섹션
사용자 입력을 HTML로 변환할 때 escapeHtml() 함수로 한 번 거름. XSS 방어 및 따옴표/태그 입력 시 발생하는 속성 손상을 방지.
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// 적용: todoDisplay()의 span, editTodo()의 input value
html += `<span ...>${escapeHtml(todos[i].text)}</span>`;핵심 포인트
- 원칙: 저장은 원본, 출력만 가공 —
todos배열에는 항상 사용자가 입력한 그대로 저장 - 변환은 HTML 생성 시점에만 —
todoDisplay()span과editTodo()input value 두 곳에서 통과 <div>같은 태그 입력 → 화면에서 사라지는 문제 해결- 따옴표 포함 입력 → input
value속성 깨지는 문제 해결
텍스트 클릭 시 span을 input으로 교체하는 방식. 별도 모달이나 페이지 이동 없이 자리에서 바로 수정 가능.
function editTodo(n) {
const span = document.querySelector(`#todo-text-${n}`);
span.outerHTML = `
<input id="todo-text-${n}" type="text" value="${escapeHtml(todos[n].text)}"
onblur="saveTodo(${n}, this.value)"
onkeydown="if(event.key==='Enter') this.blur()">
`;
}
function saveTodo(n, val) {
if (val.trim() === "") { // 공백으로 저장 시도 차단
todoDisplay(); // 원래 값으로 복원
return;
}
todos[n].text = val;
localStorage.setItem("todo", JSON.stringify(todos));
todoDisplay();
}핵심 포인트
outerHTML교체로 DOM 노드를 통째로 변경- 저장 트리거는
onblur단일 이벤트.Enter키는blur()를 호출해 동일 경로로 수렴 - 포커스 이탈(외부 클릭)도 자동 저장됨
- 공백 저장 시도는 무시하고 원래 텍스트로 복원
value속성 안의 사용자 입력도escapeHtml()통과 — 따옴표 입력 시 속성이 깨지는 문제 방지
페이지 로드 시 todoInit()이 LocalStorage에서 데이터 복원.
function todoInit() {
todos = JSON.parse(localStorage.getItem("todo")) || [];
todoDisplay();
}|| [] 패턴으로 저장된 데이터가 없을 때(null 반환) 빈 배열로 안전하게 초기화.
모든 사용자 액션은 동일한 흐름을 따름:
User Action → todos 배열 수정 → LocalStorage 저장 → todoDisplay() 재렌더링
이 일관된 패턴으로 추가/완료/수정/삭제 어떤 동작이든 상태와 화면이 항상 동기화됨.
- HTML5 · CSS3 · JavaScript (Vanilla)
- DOM Manipulation —
querySelector,innerHTML,outerHTML - LocalStorage —
setItem,getItem,JSON.stringify,JSON.parse - JSON Object handling
- Event Handling —
onclick,onkeydown,onblur
- Flexbox layout
- CSS Variables with relative units (
vw,vh,em) - Pseudo-classes —
:hover,:focus,:checked,:active - CSS Transitions — smooth hover & fade effects (
transform,opacity,box-shadow) - Google Fonts — Inter, Noto Sans KR
Made by Pyrite9




