從初期規劃,一直到後期實作功能部分的紀錄。利用之前學的透過 JavaScript 操控 DOM 元素,事件傳遞機制與監聽等
主要功能:
- 新增 todo
- 刪除 todo
- 紀錄 todo 完成與否
1. 先大概規劃好 UI,同時內心想好巢狀結構大概會怎麼寫
2. 切版,一邊想著之後要做功能,父子結構要寫好
程式碼如下:
<div class="wrapper">
<h1>What's Next 🤔</h1>
<form class="todo__input-block">
<input
class="todo__input"
type="text"
placeholder="Let's Do Something Cool..."
minlength="1"
maxlength="48"
/>
<button class="todo__add"></button>
</form>
<div class="todo__hr"></div>
<ul class="todo__list">
<li class="todo">
<label class="todo__title">
<input type="checkbox" class="todo__check" />
<p>eat an apple</p>
</label>
<button class="btn-delete"></button>
</li>
<li class="todo">
<label class="todo__title">
<input type="checkbox" class="todo__check" />
<p>write code</p>
</label>
<button class="btn-delete"></button>
</li>
<li class="todo">
<label class="todo__title">
<input type="checkbox" class="todo__check" checked />
<p>work out</p>
</label>
<button class="btn-delete"></button>
</li>
<li class="todo">
<label class="todo__title">
<input type="checkbox" class="todo__check" checked />
<p>feed dog</p>
</label>
<button class="btn-delete"></button>
</li>
<li class="todo">
<label class="todo__title">
<input type="checkbox" class="todo__check" />
<p>send postcards</p>
</label>
<button class="btn-delete"></button>
</li>
</ul>
</div>
加入 CSS 的部分:
* {
margin: 0;
padding: 0;
font-family: "Rubik", MicrosoftJhengHei, sans-serif;
/* border: 1px solid red; */
}
.wrapper {
margin: 50px auto;
max-width: 450px;
padding: 20px;
border-radius: 10px;
font-size: 22px;
letter-spacing: 1.2px;
box-shadow: 0 0 20px rgb(199 197 197);
}
h1,
h2,
h3,
h4,
p {
margin: 0;
padding: 0;
}
h1 {
text-align: center;
margin-bottom: 10px;
color: #6c6c6c;
font-size: 30px;
}
.todo__input-block {
display: flex;
justify-content: space-between;
align-items: center;
}
.todo__input {
width: 350px;
padding: 8px 12px;
font-size: 18px;
border: 1px solid #ddd;
border-radius: 50px;
letter-spacing: 1.2px;
outline: transparent;
transition: 0.2s ease-in-out;
}
::placeholder {
padding-left: 10px;
color: #b4b4b4;
}
.todo__input:hover {
border: 1px solid #686868;
}
.todo__add {
border: 1px solid #ddd;
height: 40px;
width: 40px;
border-radius: 50%;
background: transparent;
cursor: pointer;
margin-right: 5px;
outline: none;
transition: 0.2s ease-in-out;
}
.todo__add:hover {
background: #868686;
}
.todo__add:before,
.todo__add:after {
content: "";
position: absolute;
width: 20px;
height: 1px;
background: #6c6c6c;
transition: 0.2s ease-in-out;
}
.todo__add:hover:before,
.todo__add:hover:after {
background: white;
}
.todo__add:before {
transform: translate(-50%, -50%) rotate(-90deg);
}
.todo__add:after {
transform: translate(-50%, -50%);
}
.todo__hr {
border-bottom: 1px solid #ddd;
margin-top: 20px;
}
.todo__list {
list-style-type: none;
margin: 30px 20px 10px 20px;
}
.todo__title {
display: flex;
align-items: center;
}
.todo__check {
cursor: pointer;
margin-right: 12px;
min-width: 20px;
height: 20px;
border-radius: 50%;
}
.todo {
position: relative;
display: flex;
margin-bottom: 10px;
padding: 10px;
border-radius: 10px;
transition: 0.2s ease-in-out;
}
.todo__title {
flex: 1;
cursor: pointer;
padding: 0 10px;
}
.todo:hover {
background: rgb(235, 235, 235);
}
.todo__title p {
font-weight: 200;
}
/* 做與沒做 */
input:checked + p {
text-decoration: line-through #da4141 2px;
}
.btn-delete {
border: none;
width: 30px;
height: 30px;
border-radius: 50%;
background: transparent;
cursor: pointer;
outline: none;
transition: 0.2s ease-in-out;
}
.btn-delete:hover {
background: #868686;
}
.btn-delete:hover:before,
.btn-delete:hover:after {
background: #e0e0e0;
transition: 0.2s ease-in-out;
}
.btn-delete:before,
.btn-delete:after {
content: "";
background: #6c6c6c;
width: 20px;
height: 1px;
position: absolute;
}
.btn-delete:before {
transform: translate(-50%, -50%) rotate(-45deg);
}
.btn-delete:after {
transform: translate(-50%, -50%) rotate(45deg);
}
最後長這樣:
3. 實作功能
const addTodos = () => {
const inputValue = document.querySelector(".todo__input").value;
// 前後空格去掉
if (inputValue.trim().length !== 0) {
const newTodo = document.createElement("li");
newTodo.classList.add("todo");
newTodo.innerHTML = `
<label class="todo__title">
<input type="checkbox" class="todo__check" />
<p>${escapeHtml(inputValue)}</p>
</label>
<button class="btn-delete"></button>
`;
document.querySelector(".todo__list").appendChild(newTodo);
// 清空輸入欄
document.querySelector(".todo__input").value = "";
} else return;
};
// 處理特殊字元
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// click 會新增事件 document.querySelector(".todo__add").addEventListener("click", (e) => {
addTodos();
// 預防頁面更新
e.preventDefault();
});
// Enter 會新增事件
document
.querySelector(".todo__input")
.addEventListener("keypress", (e) => {
// 做不同瀏覽器的判斷
if (e.which === 13) addTodos();
});
// 刪除功能,使用事件代理
document.querySelector(".todo__list").addEventListener("click", (e) => {
const target = e.target;
// 移除 li
if (target.classList.contains("btn-delete")) {
target.parentNode.remove();
}
});
- 這邊注意利用跳脫函式 escapeHtml 處理特殊字元,防止程式碼無法正常顯示
// 處理特殊字元 function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }
注意事件代理部分,刪除 todo,點擊右側叉叉圖示,可刪除 todo
遇到問題:動態新增
由於 todo 是「動態新增」,若直接選取 .btn-delete,將無法對 後來新增的按鈕進行監聽。解決辦法:事件代理
我們可透過事件傳遞機制,改成對父元素(事件代理)進行事件監聽,如此即可對底下的所有 .btn-delete 進行監聽。
4.測試功能
恩,一切看起來很合理,沒有太誇張的部分,之後有機會就補充關於儲存在 local storage 的部分