50天50个项目,5/5完成!🌻

2025-06-07

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;
}
Enter fullscreen mode Exit fullscreen mode

实际的 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)
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

第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);
    });
};
Enter fullscreen mode Exit fullscreen mode

第43个项目:反馈UI设计

嗯,这可是个大变化!我在这个项目中学习了事件冒泡,它能避免重复 forEach 循环。事件冒泡这个名字挺有意思的,指的是事件从 DOM 树向上冒泡的过程。它不仅有趣,而且功能强大,可以节省大量代码和重复工作。本质上,你不需要针对父节点/元素的每个子元素,而是将一个 eventListener 附加到parentNodeparentElement上,这样它就会根据你设置的子元素数量进行处理。

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
    };
});
Enter fullscreen mode Exit fullscreen mode

第 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;
});
Enter fullscreen mode Exit fullscreen mode

第 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;
}
Enter fullscreen mode Exit fullscreen mode

第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>`
        }
    }
})
Enter fullscreen mode Exit fullscreen mode

例如,在第 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

第 48 个项目:随机图像输入

这是一个非常简短且直截了当的项目,与我在其他项目中已经做过的项目没有太大区别。

第 49 个项目:待办事项列表

啊啊啊,我们终于到了。笔记应用的漂亮衍生版,但我喜欢给 DOM 元素添加样式,所以最终还是挺有成就感的。和普通待办事项列表唯一不同的是,我使用了上下文菜单来执行删除操作。这意味着我只需要右键单击一个项目,它就会从 DOM 中消失,真是太方便了!另一方面,我使用常用​​的点击事件,在应用以下 CSS 代码时,画了一条线:text-decoration: line-through

.todos li.completed {
    color: grey;
    text-decoration: line-through;
}
Enter fullscreen mode Exit fullscreen mode

我还喜欢我们对 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();
    };
};
Enter fullscreen mode Exit fullscreen mode

第五十个项目:捉昆虫游戏

是的,我们成功了!虽然不是50天,但也差不多了!最后一个项目是耗时最长的项目之一,包含很多我觉得很有趣的DOM功能和操作,但我想我现在应该可以完成了……不知道你是否注意到,在项目接近尾声的时候,内容变得有点重复,但仍然非常有用且引人入胜。

总而言之,这是一次非常好的练习,我学到了很多东西,我相信在这组项目上花费超过 15/20 个小时后,我不会忘记任何事情😊

文章来源:https://dev.to/messy-ashy/50-projects-in-50-days-5-5-there-3fb2
PREV
Linux 中的简单命令让你感觉自己像个专业人士
NEXT
50天50个项目,已完成3/5!🌻