📚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_96">📚css
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_369">📚js
js_370">🐇stickynote.js
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";
// 设置最大字符长度为100
text.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();
}
});
});
});
js_566">🐇todolist.js
js">(() => {
// 锁定元素
const form = document.querySelector(".form");
const input = form.querySelector(".form__input");
const ul = document.querySelector(".toDoList");
// 代办提交时
form.addEventListener('submit', e => {
// 阻止表单的默认提交行为
e.preventDefault();
// 生成唯一的id
let 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 => {
// 获取被点击项的id
let id = e.target.getAttribute('data-id')
// 如果被点击的不是待办事项,则退出函数
if (!id) return
// 弹出确认对话框,确认是否删除该待办事项
if (confirm("喵喵大王会把它销毁哦——")) {
// 从页面中删除该待办事项
var li = document.querySelector('[data-id="' + id + '"]');
ul.removeChild(li);
}
});
})();
js_614">🐇clock.js
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) {
//单个数字补0
time.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)
更新,解决删除便利贴颜色混乱的问题
-
不用以下方法给便利贴设置颜色,这会导致删除便利贴时,颜色重置混乱
css">.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个一循环设置好颜色
js"> 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'; }