diff --git a/floating-title.js b/floating-title.js new file mode 100644 index 0000000..71a4cdd --- /dev/null +++ b/floating-title.js @@ -0,0 +1,57 @@ +// 标题飘动动画,边缘碰撞 +window.addEventListener('DOMContentLoaded', () => { + const title = document.getElementById('floating-title'); + if (!title) return; + + let x = 50, y = 50; + let vx = 1.2, vy = 0.8; + let ax = 0, ay = 0; + let lastChange = Date.now(); + + function randomizeVelocity() { + // 每隔一段时间随机微调速度,模拟自然漂浮 + if (Date.now() - lastChange > 1200) { + vx += (Math.random() - 0.5) * 0.6; + vy += (Math.random() - 0.5) * 0.6; + // 限制最大速度,避免太快 + vx = Math.max(-2, Math.min(2, vx)); + vy = Math.max(-1.5, Math.min(1.5, vy)); + lastChange = Date.now(); + } + } + + const updatePosition = () => { + const ww = window.innerWidth; + const wh = window.innerHeight; + const rect = title.getBoundingClientRect(); + + randomizeVelocity(); + + // 缓动效果 + x += vx; + y += vy; + + // 边缘碰撞检测,带一点弹性和缓冲 + if (x <= 0) { + x = 0; + vx = Math.abs(vx) * (0.7 + Math.random() * 0.3); + } + if (x + rect.width >= ww) { + x = ww - rect.width; + vx = -Math.abs(vx) * (0.7 + Math.random() * 0.3); + } + if (y <= 0) { + y = 0; + vy = Math.abs(vy) * (0.7 + Math.random() * 0.3); + } + if (y + rect.height >= wh) { + y = wh - rect.height; + vy = -Math.abs(vy) * (0.7 + Math.random() * 0.3); + } + + title.style.left = x + 'px'; + title.style.top = y + 'px'; + requestAnimationFrame(updatePosition); + }; + updatePosition(); +}); diff --git a/index.html b/index.html index 2b0930d..159c3e1 100644 --- a/index.html +++ b/index.html @@ -77,7 +77,7 @@
-

♥️ 物联网专业の在线学习平台 ♥️

+

♥️ 物联网专业の在线学习平台 ♥️

@@ -167,5 +167,7 @@
+ + diff --git a/public/images/1.jpg b/public/images/1.jpg new file mode 100644 index 0000000..9afa328 Binary files /dev/null and b/public/images/1.jpg differ diff --git a/public/images/2.jpg b/public/images/2.jpg new file mode 100644 index 0000000..f82d222 Binary files /dev/null and b/public/images/2.jpg differ diff --git a/public/images/3.jpg b/public/images/3.jpg new file mode 100644 index 0000000..73108e0 Binary files /dev/null and b/public/images/3.jpg differ diff --git a/public/images/4.jpg b/public/images/4.jpg new file mode 100644 index 0000000..aeacd98 Binary files /dev/null and b/public/images/4.jpg differ diff --git a/public/images/5.jpg b/public/images/5.jpg new file mode 100644 index 0000000..e1c1c22 Binary files /dev/null and b/public/images/5.jpg differ diff --git a/rain-icons.js b/rain-icons.js new file mode 100644 index 0000000..2596d0e --- /dev/null +++ b/rain-icons.js @@ -0,0 +1,60 @@ +// 下雨动画:让 public/icons 下的所有图片像下雨一样掉落 +const ICONS_PATH = "/icons/" +const IMAGES_PATH = "/images/" +const ICONS = [ + "material-icon-theme--folder-python-open.svg", + "material-icon-theme--python.svg", + "noto--artist-palette.svg", + "noto--bookmark-tabs.svg", + "noto--cat-face.svg", + "noto--dog-face.svg", + "noto--duck.svg", + "noto--honeybee.svg", + "noto--paintbrush.svg", +] +const IMAGES = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"] + +function createRainIcon(src, left, delay, speed, size, isImage = false) { + const img = document.createElement("img") + img.src = (isImage ? IMAGES_PATH : ICONS_PATH) + src + img.className = "rain-icon" + img.style.left = left + "px" + img.style.width = img.style.height = size + "px" + img.style.animationDelay = delay + "s" + img.style.animationDuration = speed + "s" + img.addEventListener('animationend', () => { + img.remove(); + }); + document.body.appendChild(img) +} + +function startRain() { + const ww = window.innerWidth + for (let i = 0; i < 30; i++) { + // 随机选择 icons 或 images + const useImage = Math.random() < 0.5 + if (useImage) { + const imgName = IMAGES[Math.floor(Math.random() * IMAGES.length)] + const left = Math.random() * (ww - 48) + const delay = Math.random() * 5 + const speed = 3 + Math.random() * 3 + const size = 48 + Math.random() * 32 + createRainIcon(imgName, left, delay, speed, size, true) + } else { + const icon = ICONS[Math.floor(Math.random() * ICONS.length)] + const left = Math.random() * (ww - 48) + const delay = Math.random() * 5 + const speed = 3 + Math.random() * 3 + const size = 32 + Math.random() * 32 + createRainIcon(icon, left, delay, speed, size, false) + } + } +} + +window.addEventListener("DOMContentLoaded", startRain) + +// 让动画持续下雨 + +setInterval(() => { + startRain() +}, 4000) \ No newline at end of file diff --git a/style.css b/style.css index 0b00a46..c029f34 100644 --- a/style.css +++ b/style.css @@ -384,6 +384,15 @@ a:hover { animation: centerRotate 4s linear infinite, rainbow 3s ease-in-out infinite; } +#floating-title { + position: absolute; + left: 50px; + top: 50px; + z-index: 1000; + transition: none; + user-select: none; +} + @keyframes centerRotate { 0% { transform: rotate(0deg); -webkit-transform: rotate(0deg); } 100% { transform: rotate(360deg); -webkit-transform: rotate(360deg); } @@ -721,6 +730,35 @@ a:hover { z-index: 1; } +/* 雨滴图片动画样式 */ + +.rain-icon { + position: fixed; + top: -64px; + pointer-events: none; + opacity: 0; + z-index: 9999; + animation: rainFall linear forwards; + animation-fill-mode: both; +} + + +@keyframes rainFall { + 0% { + transform: translateY(0) scale(1) rotate(0deg); + opacity: 0; + } + 10% { + opacity: 0.85; + } + 80% { + opacity: 0.85; + } + 100% { + transform: translateY(100vh) scale(0.8) rotate(360deg); + opacity: 0.1; + } +} @keyframes sparkle { 0%, 100% { transform: scale(1) rotate(0deg); -webkit-transform: scale(1) rotate(0deg); opacity: 0.7; } 50% { transform: scale(1.2) rotate(180deg); -webkit-transform: scale(1.2) rotate(180deg); opacity: 1; }