This commit is contained in:
57
floating-title.js
Normal file
57
floating-title.js
Normal file
@@ -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();
|
||||||
|
});
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<h1 class="title gradient">♥️ 物联网专业の在线学习平台 ♥️</h1>
|
<h1 id="floating-title" class="title gradient">♥️ 物联网专业の在线学习平台 ♥️</h1>
|
||||||
<h2 class="subtitle"></h2>
|
<h2 class="subtitle"></h2>
|
||||||
<div class="grid" id="sites"></div>
|
<div class="grid" id="sites"></div>
|
||||||
</main>
|
</main>
|
||||||
@@ -167,5 +167,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/main.js"></script>
|
<script type="module" src="/main.js"></script>
|
||||||
|
<script type="module" src="/floating-title.js"></script>
|
||||||
|
<script type="module" src="/rain-icons.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
BIN
public/images/1.jpg
Normal file
BIN
public/images/1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/images/2.jpg
Normal file
BIN
public/images/2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
public/images/3.jpg
Normal file
BIN
public/images/3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
public/images/4.jpg
Normal file
BIN
public/images/4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/images/5.jpg
Normal file
BIN
public/images/5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
60
rain-icons.js
Normal file
60
rain-icons.js
Normal file
@@ -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)
|
||||||
38
style.css
38
style.css
@@ -384,6 +384,15 @@ a:hover {
|
|||||||
animation: centerRotate 4s linear infinite, rainbow 3s ease-in-out infinite;
|
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 {
|
@keyframes centerRotate {
|
||||||
0% { transform: rotate(0deg); -webkit-transform: rotate(0deg); }
|
0% { transform: rotate(0deg); -webkit-transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); -webkit-transform: rotate(360deg); }
|
100% { transform: rotate(360deg); -webkit-transform: rotate(360deg); }
|
||||||
@@ -721,6 +730,35 @@ a:hover {
|
|||||||
z-index: 1;
|
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 {
|
@keyframes sparkle {
|
||||||
0%, 100% { transform: scale(1) rotate(0deg); -webkit-transform: scale(1) rotate(0deg); opacity: 0.7; }
|
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; }
|
50% { transform: scale(1.2) rotate(180deg); -webkit-transform: scale(1.2) rotate(180deg); opacity: 1; }
|
||||||
|
|||||||
Reference in New Issue
Block a user