50天50个项目,5/5完成!🌻
是的,我做到了!虽然不是50天完成50个项目,但也差不多了。每天做一个小型或中型项目不仅极大地提高了我的DOM技能,还让我明白了自律才是前进的唯一途径,因为你不会总是有动力去写代码,尤其是在天气开始变得晴朗温暖的时候。现在,让我们开始讨论我最后的几个项目吧。
第41个项目:验证账户
在这个项目中,我们使用了 Webkit,我一直推迟学习它,可能是因为我不喜欢写太多“---”😂,因为在工作中我们已经使用了 BEM(块、元素、修饰符)方法。研究了一下,我发现它是一个简单的 HTML 和 CSS Web 浏览器渲染引擎(Chrome 和 Safari 等流行浏览器都在使用)。另外,根据我从 Stack Overflow 上的一个答案了解到的信息,它是 CSS 选择器中用于属性的前缀,这些属性只希望在特定引擎上使用……很多人希望这个规范最终会消失。
.code::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
实际的 JavaScript 代码并不难,但我花了不少功夫才理解为什么我需要的按键是 keydown 而不是 keyup。是的,我仍然对某些按键事件监听器感到困惑,因为现在很多东西都已经被弃用了。
codes.forEach((code, index) => {
code.addEventListener('keydown', (e) => {
if(e.key >= 0 && e.key <= 9) {
codes[index].value = '';
setTimeout(() => codes[index + 1].focus(), 10)
} else if (e.key === 'Backspace') {
setTimeout(() => codes[index - 1].focus(), 10)
}
});
});
第42个项目:实时用户过滤器
这个项目与 GitHub 的 profiles 项目类似,不过这次我们查询的是 Randomuser API。目标是创建一个文本输入框,用户可以在其中输入姓名或地点,然后代码会根据输入的单词进行筛选,并显示包含相关信息的图片。
async function getData() {
const res = await fetch('https://randomuser.me/api?results=50')
const { results } = await res.json()
result.innerHTML = ''
results.forEach(user => {
const li = document.createElement('li');
listItems.push(li);
li.innerHTML = `
<img src="${user.picture.large}" alt="${user.name.first}">
<div class="user-info">
<h4>${user.name.first} ${user.name.last}</h4>
<p>${user.location.city}, ${user.location.country}</p>
</div>
`
result.appendChild(li);
});
};
第43个项目:反馈UI设计
嗯,这可是个大变化!我在这个项目中学习了事件冒泡,它能避免重复 forEach 循环。事件冒泡这个名字挺有意思的,指的是事件从 DOM 树向上冒泡的过程。它不仅有趣,而且功能强大,可以节省大量代码和重复工作。本质上,你不需要针对父节点/元素的每个子元素,而是将一个 eventListener 附加到parentNode或parentElement上,这样它就会根据你设置的子元素数量进行处理。
panel.addEventListener('click', (e) => {
if (e.target.parentNode.classList.contains('rating')) { //If the parent node contains rating class
removeActive();
e.target.parentNode.classList.add('active');
defaultRating = e.target.nextElementSibling.innerHTML
};
});
第 44 个项目:自定义范围滑块
这个项目比我预想的要难,有很多“边缘”情况需要考虑,比如使用 -webkit 来适配不同的浏览器,以及一些我之前不知道的新方法。
这个项目的目标是将进度条的标签移动到我拖动圆圈的方向。我在这里学到的两个新方法是:窗口的 getComputedStyle 和 CSS 方法 getPropertyValue。第一个方法返回应用于目标元素的所有 CSS 属性,第二个方法获取我想要的任何属性。
const range = document.getElementById('range');
range.addEventListener('input', (e) => {
const value = +e.target.value;
const label = e.target.nextElementSibling;
const rangeWidth = getComputedStyle(e.target).getPropertyValue('width');
const labelWidth = getComputedStyle(label).getPropertyValue('width');
const numRangeWidth = +rangeWidth.substring(0, rangeWidth.length - 2);
const numLabelWidth = +rangeWidth.substring(0, labelWidth.length - 2);
const max = e.target.max;
const min = e.target.min;
const left = value * (numRangeWidth / max) - numLabelWidth / 2;
label.style.left = `${left}px`
label.innerHTML = value;
});
第 45 个项目:Netflix 导航
这个移动菜单导航让我想起了第14个项目,大致相同,只是JavaScript代码少了一些,并增加了一个旋转效果。这个项目的目标是使用汉堡菜单图标来复制Netflix的移动菜单。
正如我之前所说,我现在已经理解了实现主要效果的一般概念,所以这里我主要关注一些较小的细节和属性,例如text-transform,它是JavaScript方法.toUpperCase()的CSS版本。我还尝试了transition-delay和text-decoration。
.nav-black {
background-color: rgb(34, 31, 31);
width: 60%;
max-width: 480px;
min-width: 320px;
transition-delay: 0.3s;
}
第46个项目:测验应用程序
这真是一次非常有趣的体验。数据被保存在一个数组中,随时可以查询,因此更容易获取数据。我练习了索引和迭代的难题,这很不错,尽管我认为让我难以区分索引和变量的原因是,索引(在我看来是整数)有时会被用字符串名称调用。
submitBtn.addEventListener('click', () => {
const answer = getSelected();
if (answer) {
if (answer === quizData[currentQuiz].correct) {
score++;
}
currentQuiz++;
if (currentQuiz < quizData.length) {
loadQuiz()
} else {
quiz.innerHTML = `<h2>You answered correctly at ${score} / ${quizData.length} questions</h2>`
}
}
})
例如,在第 4 行,我检查了之前通过 ID 获得的答案是否与我正在查看的测验页面的正确答案相对应。在这种情况下,currentQuiz 是索引,我用它来标识我在测验的哪一页。然后,索引在第 82 行增加,以移动到下一个测验。
第47个项目:见证盒
这是一个可爱的小项目,用方框显示用户评价,并带有一个进度条,让用户可以看到它消失并跳转到下一个个人资料需要多长时间。这里唯一的新东西是进度条的动画,我们通过 CSS 实现了一个 8 秒的线性无限动画,该动画在 @keyframes 中定义,这样它就能有效地在 X(水平)轴上从左侧开始从 0% 增长到 100%(如果没有设置 transform-origin,它会从进度条的中间开始,向两个方向增长)。
.progress-bar {
background-color: white;
height: 4px;
width: 100%;
animation: grow 8s linear infinite;
transform-origin: left;
}
@keyframes grow {
0% {
transform: scaleX(0);
}
}
@media(max-width: 768px) {
.fa-quote {
display: none;
}
.testimonial-container {
padding: 20px 30px;
}
}
第 48 个项目:随机图像输入
这是一个非常简短且直截了当的项目,与我在其他项目中已经做过的项目没有太大区别。
第 49 个项目:待办事项列表
啊啊啊,我们终于到了。笔记应用的漂亮衍生版,但我喜欢给 DOM 元素添加样式,所以最终还是挺有成就感的。和普通待办事项列表唯一不同的是,我使用了上下文菜单来执行删除操作。这意味着我只需要右键单击一个项目,它就会从 DOM 中消失,真是太方便了!另一方面,我使用常用的点击事件,在应用以下 CSS 代码时,画了一条线:text-decoration: line-through。
.todos li.completed {
color: grey;
text-decoration: line-through;
}
我还喜欢我们对 localStorage 进行了一些改进,通过设置和获取键值对来实现这一点。每次活动结束后,我们都会更新 localStorage,以便它反映我们所做的更改。
function updateLocalStorage() {
todosElement = document.querySelectorAll('li');
const todos = [];
todosElement.forEach(todoElement => {
todos.push({
text: todoElement.innerText,
completed: todoElement.classList.contains('completed')
});
});
localStorage.setItem('todos', JSON.stringify(todos));
};
function addTodo(todo) {
let todoText = input.value;
if (todo) {
todoText = todo.text
};
if (todoText) {
const todoElement = document.createElement('li');
if (todo && todo.completed) {
todoElement.classList.add('completed')
}
todoElement.innerText = todoText;
todoElement.addEventListener('click', () => {
todoElement.classList.toggle('completed');
updateLocalStorage();
});
todoElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
todoElement.remove();
updateLocalStorage();
});
todosUL.appendChild(todoElement);
input.value = '';
updateLocalStorage();
};
};
第五十个项目:捉昆虫游戏
是的,我们成功了!虽然不是50天,但也差不多了!最后一个项目是耗时最长的项目之一,包含很多我觉得很有趣的DOM功能和操作,但我想我现在应该可以完成了……不知道你是否注意到,在项目接近尾声的时候,内容变得有点重复,但仍然非常有用且引人入胜。
总而言之,这是一次非常好的练习,我学到了很多东西,我相信在这组项目上花费超过 15/20 个小时后,我不会忘记任何事情😊
文章来源:https://dev.to/messy-ashy/50-projects-in-50-days-5-5-there-3fb2