前端小案例 | 喵喵大王立大功 | 一个带便利贴功能的todolist面板

news/2024/11/20 4:26:00/

文章目录

  • 📚html
  • 📚css
  • 📚js
    • 🐇stickynote.js
    • 🐇todolist.js
    • 🐇clock.js
  • 📚优化更新点记录
    • 🐇解决删除便利贴颜色混乱的问题

在这里插入图片描述

📚html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>喵喵大王立大功</title><link rel="stylesheet" href="./style.css">
</head>
<body><main id="board"><!-- 代办框 --><section class="container"><!-- 标题 --><div class="heading"><img class="heading__img" src="./src/吐舌.png"><h1 class="heading__title">To-Do List</h1></div><form class="form"><div><!-- ~ Today I need to ~ --><label class="form__label" for="todo">~ Today I need to ~</label><!-- 背景音乐 --><audio src="./src/Fall.ogg" controls loop preload="metadata"></audio><!-- 待办事项输入框 --><input class="form__input"type="text" id="todo" name="to-do"size="30"required><!-- 提交按钮 --><button class="button"><span>Submit</span></button></div></form><div><!-- 代办事项列表 --><ul class="toDoList"></ul></div><div><!-- 代办框右下角那三个猫爪图片 --><img class="cute1" src="./src/cute.png"><img class="cute2" src="./src/cute2.png"><img class="cute3" src="./src/cute3.png"></div></section><!-- 时钟部分 --><div class="clock"><!-- 小时 --><div class="hours"><div class="first"><div class="number">0</div></div><div class="second"><div class="number">0</div></div></div><div class="tick">:</div><!-- 分钟 --><div class="minutes"><div class="first"><div class="number">0</div></div><div class="second"><div class="number">0</div></div></div><div class="tick">:</div><!-- 秒 --><div class="seconds"><div class="first"><div class="number">0</div></div><div class="second infinite"><div class="number">0</div></div></div></div><!-- 底下的小猫照片 --><img class="xixi" src="./src/xixi.png"></main>
</body>
<script src="https://unpkg.com/gsap@3/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3/dist/Draggable.min.js"></script>
<script src="https://assets.codepen.io/16327/InertiaPlugin.min.js"></script>
<script src="./js/stickynote.js"></script>
<script src="./js/todolist.js"></script>
<script src="./js/clock.js"></script>
</html>

📚css

@import url('https://fonts.googleapis.com/css?family=Gochi+Hand:wght@400;500;600&display=swap');
html, body {display: flex;justify-content: center;align-items: center;color: hsl(198, 1%, 29%);font-family: 'Gochi Hand', cursive;text-align: center;font-size: 130%;  
}* {padding: 0;margin: 0;
}/* 整个面板 */
#board {position: relative;/* 铺满整个视口 */width: 100vw;height: 100vh;background-color: #f1eee5;overflow: hidden;perspective: 1600px;display: grid;box-sizing: border-box;padding: 50px;
}/* 底图 */
.xixi{width: 720px;height: 120px;position: absolute;left: 50%;bottom: 0;transform: translateX(-50%);
}/* #region代办框start */
/* 整个代办框 */
.container {position: relative;height: 500px;width: 500px;background: #f1f5f8;/* 背景圆点绘制,每个重复的小方块大小为 ​25px × 25px​ */background-image: radial-gradient(#bfc0c1 7.2%, transparent 0);background-size: 25px 25px;border-radius: 20px;box-shadow: 4px 3px 7px 2px #00000040;padding: 1rem;box-sizing: border-box;/* 水平居中对齐 */margin: 0 auto;
}/* 标题 */
.heading {display: flex;align-items: center;justify-content: center;margin-bottom: 1rem;
}
/* To-Do List部分样式 */
.heading__title {transform: rotate(2deg);padding: 0.2rem 1.2rem;border-radius: 20% 5% 20% 5%/5% 20% 25% 20%;background-color: hsla(53, 100%, 93%, 0.708);font-size: 1.5rem;
}
/* 图片元素的宽度为父元素宽度的24% */
.heading__img {width: 24%;
}/* ~ Today I need to ~ */
.form__label {display: block;margin-top: -20px;margin-bottom: 0.5rem;
}
/* 音频 */
audio {width: 280px;height: 15px;margin: 0px auto;border: 1px solid #e0dfc6;border-radius: 8px;
}
/* 输入框 */
.form__input {box-sizing: border-box;background-color: transparent;padding: 0.3rem;/* 边框设置 */border-bottom-right-radius: 15px 3px;border-bottom-left-radius:3px 15px;border: solid 3px transparent;border-bottom: dashed 3px #c6beb1;/* 字体设置 */font-family: 'eryamaomiti', cursive;font-size: 1rem;color: hsla(260, 2%, 25%, 0.7);width: 70%;margin-bottom: 20px;/* 获得焦点时 */&:focus {/* 去掉默认的外边框样式 */outline: none;/* 框变为实线,颜色为#c6beb1 */border: solid 3px #c6beb1;}
}/* submit按钮 */
.button {padding: 0;border: none;/* 顺时针旋转4度 */transform: rotate(4deg);/* 变换的起点为中心点 */transform-origin: center;font-family: 'eryamaomiti', cursive;text-decoration: none;padding-bottom: 3px;border-radius: 5px;/* 添加一个垂直的盒子阴影效果 */box-shadow: 0 2px 0 hsl(198, 1%, 29%);/* 过渡效果的时间和缓动函数 */transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);/* Base64编码的背景图像 */background-image: url('data:image/gif;base64,R0lGODlhBAAEAIABAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjEgNjQuMTQwOTQ5LCAyMDEwLzEyLzA3LTEwOjU3OjAxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93cyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5NUY1OENCRDdDMDYxMUUyOTEzMEE1MEM5QzM0NDVBMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo5NUY1OENCRTdDMDYxMUUyOTEzMEE1MEM5QzM0NDVBMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjk1RjU4Q0JCN0MwNjExRTI5MTMwQTUwQzlDMzQ0NUEzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjk1RjU4Q0JDN0MwNjExRTI5MTMwQTUwQzlDMzQ0NUEzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAAQAsAAAAAAQABAAAAgYEEpdoeQUAOw==');background-color: hsla(0, 0%, 100%, 0.7);
}
/* 按钮内文本样式 */
.button span {background: #f1f5f8;display: block;padding: 0.5rem 1rem;border-radius: 5px;border: 2px solid hsl(198, 1%, 29%);
}
/* 按钮在激活状态和获取焦点时 */
.button:active, .button:focus {transform: translateY(4px);padding-bottom: 0px;outline: 0;
}/* 代办事项列表 */
.toDoList {padding-left: 2.5rem;text-align: left;
}
li {position: relative;padding-top: 0.2rem;font-size: 24px;color: #3c4654;font-family: 'eryamaomiti', cursive;
}
/* 悬停时添加删除线效果 */
li:hover {text-decoration: line-through #d5c8a0;
}/* 右下角的三个爪子 */
.cute1{position: absolute; bottom: 5px; right: 70px; width: 100px;height: 100px;
}
.cute2{position: absolute; bottom: 70px; right: 5px; width: 100px;height: 100px;
}
.cute3{position: absolute; bottom: 0; right: 0; width: 100px;height: 100px;
}
/* #endregion代办框end *//* #region便利贴start */
/* 便利贴样式 */
.stickynote {position: absolute;width: 200px;height: 200px;box-sizing: border-box;padding: 10px;transform: rotateX(5deg);box-shadow: -1px 10px 5px -4px rgba(0, 0, 0, 0.02), inset 0 24px 30px -12px rgba(0, 0, 0, 0.2);/* 之后便利贴内文本框居中 */display: flex;justify-content: center;align-items: center;
}
/* 便利贴文本框 */
.stickynote-text {border-radius: 10px;color: #686a67;font-size: 20px;font-weight: 400;border: none;background: transparent;outline: none;text-align: center;resize: none;overflow: hidden;font-family: 'eryamaomiti', cursive;
}
/* 获得焦点时 */
.stickynote-text:focus {background-color: rgba(0,0,0,0.2);
}
/* 占位符(输入文本前的文本显示) */
.stickynote-text::placeholder {color: #686a67;opacity: 30%;
}
/* #endregion便利贴end *//* #region时钟start */
.clock {margin: -80px auto;height: 10vh;color: #e2a2aca1;font-size: 10vh;font-family: 'eryamaomiti';line-height: 10vh;display: flex;position: relative;overflow: hidden;
}
.clock > div {display: flex;
}
.tick {line-height: 7vh;
}
.tick-hidden {opacity: 0;
}
/* 线性过渡,持续时间为1s */
.move {animation: move linear 1s infinite;
}
@keyframes move {from {transform: translateY(0vh);}to {transform: translateY(-10vh);}
}
/* #endregion时钟end */

📚js

🐇stickynote.js

// 获取id为board的元素
const board = document.querySelector("#board"); // 循环创建102个便利贴
for (let i = 0; i < 102; i++) {// 创建div元素作为便利贴的容器,并添加类名"stickynote"const sticky = document.createElement("div"); sticky.classList.add("stickynote");  if((i + 1) % 6 == 0){// 红sticky.style.background='#f0c2a2';}else if((i + 1) % 6 == 5){// 黄sticky.style.background='#fffbc7';}else if((i + 1) % 6 == 4){// 蓝sticky.style.background='#aed0ee';}else if((i + 1) % 6 == 3){// 粉sticky.style.background='#f9d3e3';}else if((i + 1) % 6 == 2){// 绿sticky.style.background='#bfd1b1';}else if((i + 1) % 6 == 1){// 紫sticky.style.background='#dcc7e1';}// 创建一个textarea元素作为便利贴的文本框const text = document.createElement("textarea"); // 设置输入类型为文本text.type = "text"; // 设置占位符文本为"Drag Me"text.placeholder = "Drag Me"; // 设置最大字符长度为100text.maxLength = 100; // 添加类名"stickynote-text"到便利贴文本框text.classList.add("stickynote-text"); // 将文本框添加到便利贴容器中sticky.appendChild(text); // 将便利贴容器添加到板块中board.appendChild(sticky); 
}// 自适应便利贴文本框高度
document.querySelectorAll('textarea').forEach(textarea => {function setHeight() {// 先重置高度textarea.style.height = 'auto'; // 根据实际内容设置高度textarea.style.height = `${textarea.scrollHeight}px`; }setHeight();// 输入时自动调整高度textarea.addEventListener('input', setHeight);// 内容改变时自动调整高度textarea.addEventListener('change', setHeight); 
});// 创建可拖动便利贴的对象
const draggables = Draggable.create(".stickynote", {// 设置拖动方向为水平和垂直type: "x,y", // 拖动开始时的回调函数onDragStart: function () { // 启用惯性动画,外部js库InertiaPlugin.track(this.target, "x"); // 拖动开始时的动画效果grabNoteAnimation(this.target); const inputField = this.target.querySelector('.stickynote-text');// 修改文本框的占位符文本inputField.placeholder = "Stick Me"; },// 拖动中的回调函数onDrag: function () { // 获取水平方向上的速度let dx = InertiaPlugin.getVelocity(this.target, "x"); // 调用GSAP库gsap.to(this.target, { // 根据速度旋转便利贴(所以会有越快越歪)rotation: dx * -0.003, duration: 0.5,ease: "elastic.out(1.8, 0.6)",// 动画完成后的回调函数onComplete: function () { // 旋转回初始状态gsap.to(this.target, { rotation: 0,duration: 0.5,ease: "elastic.out(1.8, 0.6)"});}});},// 拖动结束时的回调函数onDragEnd: function () {releaseNoteAnimation(this.target); const inputField = this.target.querySelector('.stickynote-text');// 贴好了,就是"Write On Me"inputField.placeholder = "Write On Me"; },// 避免拖动时误触发内部元素的点击事件dragClickables: false, 
});// 拖动便利贴时的抓取动画
function grabNoteAnimation(target) {// 创建动画时间线对象const timeline = gsap.timeline(); timeline.to(target, {rotateX: 30, boxShadow: "-1px 14px 40px -4px rgba(0, 0, 0, 0.12), inset 0 14px 20px -12px rgba(0, 0, 0, 0.3)", // 添加阴影效果duration: 0.3}).to(target, {// 将便利贴旋转回初始状态rotation: 0, rotateX: 5,// 缩放便利贴scale: 1.1, boxShadow: "-1px 14px 40px -4px rgba(0, 0, 0, 0.12), inset 0 24px 30px -12px rgba(0, 0, 0, 0.3)", // 调整阴影效果// 弹性缓动效果ease: "elastic.out(0.8, 0.5)" }, 0.15);timeline.play();
}// 释放便利贴时的动画
function releaseNoteAnimation(target) {const timeline = gsap.timeline(); timeline.to(target, {rotateX: 30, boxShadow: "-1px 10px 5px -4px rgba(0, 0, 0, 0.02), inset 0 24px 30px -12px rgba(0, 0, 0, 0.2)", // 调整阴影效果duration: 0.3}).to(target, {// 还原缩放scale: 1 }, 0).to(target, {// 将便利贴旋转回初始状态rotateX: 5, boxShadow: "-1px 10px 5px -4px rgba(0, 0, 0, 0.02), inset 0 24px 30px -12px rgba(0, 0, 0, 0.2)", // 调整阴影效果ease: "elastic.out(0.8, 0.5)"}, 0.2);timeline.play();
}// 双击便利贴删除
document.querySelectorAll(".stickynote").forEach((sticky) => {sticky.addEventListener("dblclick", function() {const dragInstance = draggables.find((instance) => instance.target === sticky);if (dragInstance) {dragInstance.disable();}sticky.remove();});
});// 输入框获得焦点时禁用拖动
// 输入框失去焦点时重新启用拖动
document.querySelectorAll(".stickynote-text").forEach((textField) => {textField.addEventListener("focus", () => {draggables.forEach((instance) => {if (instance.target.contains(textField)) {instance.disable();}});});textField.addEventListener("blur", () => {draggables.forEach((instance) => {if (instance.target.contains(textField)) {instance.enable();}});});
});

🐇todolist.js

(() => { // 锁定元素const form = document.querySelector(".form"); const input = form.querySelector(".form__input"); const ul = document.querySelector(".toDoList"); // 代办提交时form.addEventListener('submit', e => {// 阻止表单的默认提交行为e.preventDefault(); // 生成唯一的idlet itemId = String(Date.now()); // 获取输入框中的待办事项内容let toDoItem = input.value; // 如果待办事项列表已经有6个或以上的项if (ul.children.length >= 6) { alert("喵喵大王处理不过来啦!"); return;}// 将待办事项添加到页面中const li = document.createElement('li'); li.setAttribute("data-id", itemId); li.innerText = toDoItem; ul.appendChild(li);// 清空输入框input.value = ''; });// 点击删除时ul.addEventListener('click', e => {// 获取被点击项的idlet id = e.target.getAttribute('data-id') // 如果被点击的不是待办事项,则退出函数if (!id) return // 弹出确认对话框,确认是否删除该待办事项if (confirm("喵喵大王会把它销毁哦——")) { // 从页面中删除该待办事项var li = document.querySelector('[data-id="' + id + '"]'); ul.removeChild(li); }});
})();

🐇clock.js

// 锁定元素
var hoursContainer = document.querySelector('.hours') 
var minutesContainer = document.querySelector('.minutes') 
var secondsContainer = document.querySelector('.seconds') 
var tickElements = Array.from(document.querySelectorAll('.tick')) // 保存上一次时间,初始值为0。
var last = new Date(0) 
// 设置为-1,以确保首次更新所有时间显示
last.setUTCHours(-1) 
// 用于记录动画效果的状态
var tickState = true // 更新时间
function updateTime () {var now = new Date var lastHours = last.getHours().toString() var nowHours = now.getHours().toString()// 如果上一次时间的小时和当前时间的小时不相等if (lastHours !== nowHours) {// 更新小时的显示updateContainer(hoursContainer, nowHours) }// 分钟和秒同理var lastMinutes = last.getMinutes().toString() var nowMinutes = now.getMinutes().toString() if (lastMinutes !== nowMinutes) { updateContainer(minutesContainer, nowMinutes) }var lastSeconds = last.getSeconds().toString() var nowSeconds = now.getSeconds().toString() if (lastSeconds !== nowSeconds) {updateContainer(secondsContainer, nowSeconds) }// 更新上一次时间为当前时间last = now 
}// 切换'tick'元素的CSS类'tick-hidden'以实现闪烁效果
function tick () {tickElements.forEach(t => t.classList.toggle('tick-hidden')) 
}// 更新显示
function updateContainer (container, newTime) {var time = newTime.split('') if (time.length === 1) { //单个数字补0time.unshift('0') }// 更新显示var first = container.firstElementChild if (first.lastElementChild.textContent !== time[0]) { updateNumber(first, time[0]) }var last = container.lastElementChild if (last.lastElementChild.textContent !== time[1]) { updateNumber(last, time[1])}
}// 变更动画实现
function updateNumber (element, number) {var second = element.lastElementChild.cloneNode(true) second.textContent = number element.appendChild(second) element.classList.add('move') setTimeout(function () {element.classList.remove('move')}, 975)//选用975而非1000会更平滑setTimeout(function () {element.removeChild(element.firstElementChild) }, 975)
}setInterval(updateTime, 100) 

📚优化更新点记录

🐇解决删除便利贴颜色混乱的问题

  • 不用以下方法给便利贴设置颜色,这会导致删除便利贴时,颜色重置混乱

    .stickynote:nth-child(n) {background: #dcc7e1;
    }
    .stickynote:nth-child(2n) {background: #bfd1b1;
    }
    .stickynote:nth-child(3n) {background: #f9d3e3;
    }
    .stickynote:nth-child(4n) {background: #aed0ee;
    }
    .stickynote:nth-child(5n) {background: #fffbc7;
    }
    .stickynote:nth-child(6n) {background: #f0c2a2;
    }
    
  • 直接在js生成便利贴时,6个一循环设置好颜色

     if((i + 1) % 6 == 0){// 红sticky.style.background='#f0c2a2';}else if((i + 1) % 6 == 5){// 黄sticky.style.background='#fffbc7';}else if((i + 1) % 6 == 4){// 蓝sticky.style.background='#aed0ee';}else if((i + 1) % 6 == 3){// 粉sticky.style.background='#f9d3e3';}else if((i + 1) % 6 == 2){// 绿sticky.style.background='#bfd1b1';}else if((i + 1) % 6 == 1){// 紫sticky.style.background='#dcc7e1';}
    

喵喵大王立大功


http://www.ppmy.cn/news/1198055.html

相关文章

Linux accept和FD_xxx的使用

Linux socket accept功能的作用是在服务器端等待并接受客户端的连接请求。当有客户端尝试连接服务器时&#xff0c;服务器调用accept函数来接受该连接请求&#xff0c;并创建一个新的socket来与该客户端进行通信。 具体来说&#xff0c;accept函数被动监听客户端的三次握手连接…

Voice vlan、ICMP、单臂路由、mux-vlan

目录 一&#xff0c;Voice VLAN Voice vlan配置命令 一&#xff0c;问&#xff1a;已知网络中一台服务器的IP地址&#xff0c;如何找到这太服务器在哪台交换机的哪个接口上​编辑 思路&#xff1a; 二&#xff0c;ICMP协议 三&#xff0c;ICMP案例分析​编辑 四&#xf…

pycharm插件推荐:一款能够根据上下文自动提示帮写代码的AI插件

直接上插件&#xff1a; 这个插件有多牛&#xff01;他能够根据注释帮你直接补全代码&#xff08;只需要你按一下tab键&#xff09;&#xff0c;甚至还有学习的能力。 如下&#xff1a; 我注释写完后&#xff0c;一回车就模糊的写出了预计的代码&#xff0c;只要我按下tab键…

0005Java安卓程序设计-ssm基于Android的网店系统

文章目录 **摘要**目录系统设计开发环境 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 摘要 随着Internet的发展&#xff0c;人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化&#xff0c;网络化和电子化。网上管…

大数据之陌陌聊天数据分析案例

目录 目标需求 数据内容 基于Hive数仓实现需求开发 1.建库建表、加载数据 2.ETL数据清洗 3需求指标统计 目标需求 基于Hadoop和hive实现聊天数据统计分析&#xff0c;构建聊天数据分析报表 1.统计今日总消息量 2.统计今日每小时消息量&#xff0c;发送和接收用户数 3.…

2024最新mac电脑清理垃圾的软件有哪些?

mac电脑是许多人喜爱的电子产品&#xff0c;它拥有优美的设计、流畅的操作系统和强大的性能。但是&#xff0c;随着使用时间的增长&#xff0c;mac电脑也会积累一些不必要的垃圾文件&#xff0c;这些文件会占用宝贵的存储空间&#xff0c;影响电脑的运行速度和稳定性。因此&…

本地部署Jellyfin影音服务器并实现远程访问影音库

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

【C++那些事儿】类与对象(1)

君兮_的个人主页 即使走的再远&#xff0c;也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;我之前看过一套书叫做《明朝那些事儿》&#xff0c;把本来枯燥的历史讲的生动有趣。而C作为一门接近底层的语言&#xff0c;无疑是抽象且难度颇…