feat: 添加歌曲请求站页面及静态资源
style: 调整标签气泡动效样式和布局 refactor: 优化标签碰撞检测算法和位置计算 docs: 更新README文件说明 chore: 添加相关图片和CSS文件资源
256
song-request-station copy.html
Normal file
@@ -0,0 +1,256 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>标签气泡动效</title>
|
||||
<style></style>
|
||||
|
||||
<link rel="stylesheet" href="./static/css/song-request-station.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div class="container" id="bubbleContainer"></div> -->
|
||||
<div class="container">
|
||||
<div class="container-box mar1200">
|
||||
<img class="logo" src="./static/img/logo.png" alt="" />
|
||||
<div class="header">
|
||||
<img class="halo" src="./static/img/halo.png" />
|
||||
<img class="record-black" src="./static/img/record-black.svg" />
|
||||
<div class="record-circle"></div>
|
||||
<img class="star-icon" src="./static/img/star-icon.png" alt="" />
|
||||
<img class="bj-2" src="./static/img/song-request-bj-2.svg" alt="" />
|
||||
<img class="love-little" src="./static/img/love-little.svg" alt="" />
|
||||
<img class="music-icon" src="./static/img/music-icon.svg" alt="" />
|
||||
<img class="bj" src="./static/img/song-request-bj.svg" alt="" />
|
||||
<img class="love-big" src="./static/img/love-big.svg" alt="" />
|
||||
<img class="music-score" src="./static/img/music-score.png" />
|
||||
<img class="robot" src="./static/img/robot.png" />
|
||||
<img class="text" src="./static/img/song-request-text.svg" />
|
||||
<img class="face" src="./static/img/smiling-face.png" />
|
||||
<img class="star-icon-2" src="./static/img/star-icon-2.png" />
|
||||
<img class="ai-music" src="./static/img/ai-music.png" />
|
||||
<img class="green-glow" src="./static/img/green-glow.png" />
|
||||
<img class="shadow" src="./static/img/shadow.png" />
|
||||
</div>
|
||||
<div class="list-box">
|
||||
<div class="list" id="bubbleContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 标签数据
|
||||
const tags = ["前端开发", "JavaScript", "CSS动画", "HTML5", "React", "Vue", "TypeScript", "Node.js", "UI设计", "用户体验", "响应式布局", "性能优化", "微信小程序", "PWA", "Canvas", "SVG", "WebGL", "数据可视化", "模块化", "组件化", "", "", "", "", ""];
|
||||
|
||||
// 获取容器
|
||||
const container = document.getElementById("bubbleContainer");
|
||||
|
||||
// 创建空白标签数组,用于存储所有空白标签的信息
|
||||
const emptyTags = [];
|
||||
|
||||
// 存储所有标签的位置和大小信息
|
||||
const allTags = [];
|
||||
const tagSizes = []; // 存储每个标签的实际尺寸
|
||||
const defaultTagWidth = 120; // 默认标签宽度
|
||||
const defaultTagHeight = 40; // 默认标签高度
|
||||
|
||||
// 计算容器尺寸
|
||||
function getContainerDimensions() {
|
||||
const containerWidth = container.offsetWidth;
|
||||
const containerHeight = container.offsetHeight;
|
||||
return { containerWidth, containerHeight };
|
||||
}
|
||||
|
||||
// 获取标签的实际尺寸
|
||||
function getTagDimensions(tagText) {
|
||||
// 创建一个临时标签来测量实际尺寸
|
||||
const tempTag = document.createElement("div");
|
||||
tempTag.className = "bubble-tag";
|
||||
tempTag.textContent = tagText;
|
||||
tempTag.style.position = "absolute";
|
||||
tempTag.style.visibility = "hidden";
|
||||
tempTag.style.left = "-9999px";
|
||||
tempTag.style.top = "-9999px";
|
||||
|
||||
document.body.appendChild(tempTag);
|
||||
const width = tempTag.offsetWidth;
|
||||
const height = tempTag.offsetHeight;
|
||||
document.body.removeChild(tempTag);
|
||||
|
||||
// 确保至少有默认的尺寸
|
||||
return {
|
||||
width: Math.max(width, defaultTagWidth),
|
||||
height: Math.max(height, defaultTagHeight),
|
||||
};
|
||||
}
|
||||
|
||||
// 检查两个标签是否碰撞
|
||||
function isColliding(tag1, tag2, padding = 20) {
|
||||
// 计算两个标签之间的距离
|
||||
const dx = Math.abs(tag1.x + tag1.width / 2 - (tag2.x + tag2.width / 2));
|
||||
const dy = Math.abs(tag1.y + tag1.height / 2 - (tag2.y + tag2.height / 2));
|
||||
|
||||
// 如果距离小于两个标签半径之和加上内边距,则发生碰撞
|
||||
return dx < tag1.width / 2 + tag2.width / 2 + padding && dy < tag1.height / 2 + tag2.height / 2 + padding;
|
||||
}
|
||||
|
||||
// 计算理想的网格大小以实现均匀分布
|
||||
function calculateGridSize(totalTags, containerWidth, containerHeight, avgTagSize) {
|
||||
// 估计每个标签需要的空间(包括间距)
|
||||
const tagSpacing = avgTagSize + 60; // 标签直径 + 间距
|
||||
|
||||
// 计算大致的行数和列数
|
||||
const idealColumns = Math.ceil(Math.sqrt((totalTags * containerWidth) / containerHeight));
|
||||
const idealRows = Math.ceil(totalTags / idealColumns);
|
||||
|
||||
// 确保网格不会太拥挤
|
||||
const actualColumns = Math.min(idealColumns, Math.floor(containerWidth / tagSpacing));
|
||||
const actualRows = Math.min(idealRows, Math.floor(containerHeight / tagSpacing));
|
||||
|
||||
return { columns: Math.max(1, actualColumns), rows: Math.max(1, actualRows) };
|
||||
}
|
||||
|
||||
// 为新标签找到一个不与其他标签碰撞的位置,优先使用均匀分布
|
||||
function findNonCollidingPosition(tagWidth, tagHeight, containerWidth, containerHeight, totalTags, currentIndex) {
|
||||
const maxAttempts = 200;
|
||||
let attempts = 0;
|
||||
|
||||
// 计算网格大小
|
||||
const avgTagSize = (tagWidth + tagHeight) / 2;
|
||||
const { columns, rows } = calculateGridSize(totalTags, containerWidth, containerHeight, avgTagSize);
|
||||
|
||||
// 首先尝试网格均匀分布位置
|
||||
while (attempts < maxAttempts * 0.7) {
|
||||
attempts++;
|
||||
|
||||
// 计算理想的网格位置
|
||||
const gridX = (currentIndex % columns) * (containerWidth / columns);
|
||||
const gridY = Math.floor(currentIndex / columns) * (containerHeight / rows);
|
||||
|
||||
// 添加一些随机偏移,避免完全规则排列,但保持大致均匀
|
||||
const randomOffset = 0.2; // 20%的随机偏移
|
||||
const xOffset = (((Math.random() - 0.5) * containerWidth) / columns) * randomOffset;
|
||||
const yOffset = (((Math.random() - 0.5) * containerHeight) / rows) * randomOffset;
|
||||
|
||||
// 计算最终位置,确保不会超出容器
|
||||
const x = Math.max(0, Math.min(containerWidth - tagWidth, gridX + xOffset));
|
||||
const y = Math.max(0, Math.min(containerHeight - tagHeight, gridY + yOffset));
|
||||
|
||||
const newTag = { x, y, width: tagWidth, height: tagHeight };
|
||||
|
||||
// 检查与所有已有标签是否碰撞
|
||||
let colliding = false;
|
||||
for (let i = 0; i < allTags.length; i++) {
|
||||
if (isColliding(newTag, allTags[i])) {
|
||||
colliding = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!colliding) {
|
||||
return { x, y };
|
||||
}
|
||||
}
|
||||
|
||||
// 如果网格分布尝试失败,回退到随机位置搜索
|
||||
while (attempts < maxAttempts) {
|
||||
attempts++;
|
||||
|
||||
// 随机生成一个位置
|
||||
const x = Math.random() * (containerWidth - tagWidth);
|
||||
const y = Math.random() * (containerHeight - tagHeight);
|
||||
const newTag = { x, y, width: tagWidth, height: tagHeight };
|
||||
|
||||
// 检查与所有已有标签是否碰撞
|
||||
let colliding = false;
|
||||
for (let i = 0; i < allTags.length; i++) {
|
||||
if (isColliding(newTag, allTags[i])) {
|
||||
colliding = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!colliding) {
|
||||
return { x, y };
|
||||
}
|
||||
}
|
||||
|
||||
// 如果尝试次数用完仍然找不到完全不碰撞的位置,返回随机位置
|
||||
return {
|
||||
x: Math.random() * (containerWidth - tagWidth),
|
||||
y: Math.random() * (containerHeight - tagHeight),
|
||||
};
|
||||
}
|
||||
|
||||
// 创建标签并添加动画
|
||||
function createTags() {
|
||||
const { containerWidth, containerHeight } = getContainerDimensions();
|
||||
const totalTags = tags.length;
|
||||
|
||||
// 清空容器
|
||||
container.innerHTML = "";
|
||||
allTags.length = 0;
|
||||
tagSizes.length = 0;
|
||||
|
||||
tags.forEach((tagText, index) => {
|
||||
// 创建标签元素
|
||||
const tag = document.createElement("div");
|
||||
tag.className = "bubble-tag";
|
||||
tag.textContent = tagText;
|
||||
|
||||
// 获取标签的实际尺寸
|
||||
const { width, height } = getTagDimensions(tagText);
|
||||
tagSizes.push({ width, height });
|
||||
|
||||
// 查找不碰撞的位置
|
||||
const { x, y } = findNonCollidingPosition(width, height, containerWidth, containerHeight, totalTags, index);
|
||||
tag.style.left = `${x}px`;
|
||||
tag.style.top = `${y}px`;
|
||||
|
||||
// 保存标签信息
|
||||
allTags.push({ x, y, width, height, element: tag });
|
||||
|
||||
// 随机大小
|
||||
const randomSize = 0.9 + Math.random() * 0.4; // 稍微增大了最小尺寸
|
||||
tag.style.transform = `scale(${randomSize})`;
|
||||
|
||||
// 随机动画延迟
|
||||
const delay = Math.random() * 5;
|
||||
tag.style.animationDelay = `${delay}s`;
|
||||
|
||||
// 随机动画持续时间
|
||||
const duration = 10 + Math.random() * 10; // 延长动画周期,使效果更流畅
|
||||
tag.style.animation = `float ${duration}s infinite ease-in-out, pulse 3s infinite alternate`;
|
||||
|
||||
// 随机背景色 - 模仿截图中的颜色范围
|
||||
const hue = 250 + Math.random() * 60; // 紫色到粉色的范围
|
||||
const lightness = 85 + Math.random() * 10; // 亮度调整
|
||||
tag.style.backgroundColor = `hsla(${hue}, 70%, ${lightness}%, 0.9)`;
|
||||
|
||||
// 添加到容器
|
||||
container.appendChild(tag);
|
||||
|
||||
// 添加点击效果
|
||||
tag.addEventListener("click", () => {
|
||||
tag.style.animation = "none";
|
||||
tag.style.transform = "scale(1.3)";
|
||||
tag.style.zIndex = "10";
|
||||
setTimeout(() => {
|
||||
tag.style.animation = `float ${duration}s infinite ease-in-out, pulse 3s infinite alternate`;
|
||||
tag.style.animationDelay = "0s";
|
||||
tag.style.zIndex = "1";
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 窗口大小变化时重新创建标签
|
||||
window.addEventListener("resize", () => {
|
||||
createTags();
|
||||
});
|
||||
|
||||
// 初始创建标签
|
||||
createTags();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
287
song-request-station.html
Normal file
@@ -0,0 +1,287 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>标签气泡动效</title>
|
||||
<style></style>
|
||||
|
||||
<link rel="stylesheet" href="./static/css/song-request-station.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div class="container" id="bubbleContainer"></div> -->
|
||||
<div class="container">
|
||||
<div class="container-box mar1200">
|
||||
<img class="logo" src="./static/img/logo.png" alt="" />
|
||||
<div class="header">
|
||||
<img class="halo" src="./static/img/halo.png" />
|
||||
<img class="record-black" src="./static/img/record-black.svg" />
|
||||
<div class="record-circle"></div>
|
||||
<img class="star-icon" src="./static/img/star-icon.png" alt="" />
|
||||
<img class="bj-2" src="./static/img/song-request-bj-2.svg" alt="" />
|
||||
<img class="love-little" src="./static/img/love-little.svg" alt="" />
|
||||
<img class="music-icon" src="./static/img/music-icon.svg" alt="" />
|
||||
<img class="bj" src="./static/img/song-request-bj.svg" alt="" />
|
||||
<img class="love-big" src="./static/img/love-big.svg" alt="" />
|
||||
<img class="music-score" src="./static/img/music-score.png" />
|
||||
<img class="robot" src="./static/img/robot.png" />
|
||||
<img class="text" src="./static/img/song-request-text.svg" />
|
||||
<img class="face" src="./static/img/smiling-face.png" />
|
||||
<img class="star-icon-2" src="./static/img/star-icon-2.png" />
|
||||
<img class="ai-music" src="./static/img/ai-music.png" />
|
||||
<img class="green-glow" src="./static/img/green-glow.png" />
|
||||
<img class="shadow" src="./static/img/shadow.png" />
|
||||
</div>
|
||||
<div class="list-box">
|
||||
<div class="list" id="bubbleContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 标签数据
|
||||
const tags = ["前端开发", "JavaScript", "CSS动画", "HTML5", "React", "Vue", "TypeScript", "Node.js", "UI设计", "用户体验", "响应式布局", "性能优化", "微信小程序", "PWA", "Canvas", "SVG", "WebGL", "数据可视化", "模块化", "组件化", "", "", "", "", ""];
|
||||
|
||||
// 获取容器
|
||||
const container = document.getElementById("bubbleContainer");
|
||||
|
||||
// 创建空白标签数组,用于存储所有空白标签的信息
|
||||
const emptyTags = [];
|
||||
|
||||
// 存储所有标签的位置和大小信息
|
||||
const allTags = [];
|
||||
const tagSizes = []; // 存储每个标签的实际尺寸
|
||||
const defaultTagWidth = 120; // 默认标签宽度
|
||||
const defaultTagHeight = 40; // 默认标签高度
|
||||
|
||||
// 计算容器尺寸
|
||||
function getContainerDimensions() {
|
||||
const containerWidth = container.offsetWidth;
|
||||
const containerHeight = container.offsetHeight;
|
||||
return { containerWidth, containerHeight };
|
||||
}
|
||||
|
||||
// 获取标签的实际尺寸
|
||||
function getTagDimensions(tagText) {
|
||||
// 创建一个临时标签来测量实际尺寸
|
||||
const tempTag = document.createElement("div");
|
||||
tempTag.className = "bubble-tag";
|
||||
tempTag.textContent = tagText;
|
||||
tempTag.style.position = "absolute";
|
||||
tempTag.style.visibility = "hidden";
|
||||
tempTag.style.left = "-9999px";
|
||||
tempTag.style.top = "-9999px";
|
||||
|
||||
document.body.appendChild(tempTag);
|
||||
const width = tempTag.offsetWidth;
|
||||
const height = tempTag.offsetHeight;
|
||||
document.body.removeChild(tempTag);
|
||||
|
||||
// 确保至少有默认的尺寸
|
||||
return {
|
||||
width: Math.max(width, defaultTagWidth),
|
||||
height: Math.max(height, defaultTagHeight),
|
||||
};
|
||||
}
|
||||
|
||||
// 检查两个标签是否碰撞
|
||||
function isColliding(tag1, tag2, padding = 20) {
|
||||
// 判断是否为空白标签
|
||||
const isTag1Empty = tag1.isEmpty === true;
|
||||
const isTag2Empty = tag2.isEmpty === true;
|
||||
|
||||
// 如果其中一个是空白标签,允许它们重叠
|
||||
if (isTag1Empty || isTag2Empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算两个标签之间的距离
|
||||
const dx = Math.abs(tag1.x + tag1.width / 2 - (tag2.x + tag2.width / 2));
|
||||
const dy = Math.abs(tag1.y + tag1.height / 2 - (tag2.y + tag2.height / 2));
|
||||
|
||||
// 如果距离小于两个标签半径之和加上内边距,则发生碰撞
|
||||
return dx < tag1.width / 2 + tag2.width / 2 + padding && dy < tag1.height / 2 + tag2.height / 2 + padding;
|
||||
}
|
||||
|
||||
// 计算理想的网格大小以实现均匀分布
|
||||
function calculateGridSize(totalTags, containerWidth, containerHeight, avgTagSize) {
|
||||
// 估计每个标签需要的空间(包括间距)
|
||||
const tagSpacing = avgTagSize + 60; // 标签直径 + 间距
|
||||
|
||||
// 计算大致的行数和列数
|
||||
const idealColumns = Math.ceil(Math.sqrt((totalTags * containerWidth) / containerHeight));
|
||||
const idealRows = Math.ceil(totalTags / idealColumns);
|
||||
|
||||
// 确保网格不会太拥挤
|
||||
const actualColumns = Math.min(idealColumns, Math.floor(containerWidth / tagSpacing));
|
||||
const actualRows = Math.min(idealRows, Math.floor(containerHeight / tagSpacing));
|
||||
|
||||
return { columns: Math.max(1, actualColumns), rows: Math.max(1, actualRows) };
|
||||
}
|
||||
|
||||
// 为新标签找到一个不与其他标签碰撞的位置,优先使用均匀分布
|
||||
function findNonCollidingPosition(tagWidth, tagHeight, containerWidth, containerHeight, totalTags, currentIndex) {
|
||||
const maxAttempts = 200;
|
||||
let attempts = 0;
|
||||
|
||||
// 计算网格大小
|
||||
const avgTagSize = (tagWidth + tagHeight) / 2;
|
||||
const { columns, rows } = calculateGridSize(totalTags, containerWidth, containerHeight, avgTagSize);
|
||||
|
||||
// 首先尝试网格均匀分布位置
|
||||
while (attempts < maxAttempts * 0.7) {
|
||||
attempts++;
|
||||
|
||||
// 计算理想的网格位置
|
||||
const gridX = (currentIndex % columns) * (containerWidth / columns);
|
||||
const gridY = Math.floor(currentIndex / columns) * (containerHeight / rows);
|
||||
|
||||
// 添加一些随机偏移,避免完全规则排列,但保持大致均匀
|
||||
const randomOffset = 0.2; // 20%的随机偏移
|
||||
const xOffset = (((Math.random() - 0.5) * containerWidth) / columns) * randomOffset;
|
||||
const yOffset = (((Math.random() - 0.5) * containerHeight) / rows) * randomOffset;
|
||||
|
||||
// 计算最终位置,确保不会超出容器
|
||||
const x = Math.max(0, Math.min(containerWidth - tagWidth, gridX + xOffset));
|
||||
const y = Math.max(0, Math.min(containerHeight - tagHeight, gridY + yOffset));
|
||||
|
||||
const newTag = { x, y, width: tagWidth, height: tagHeight };
|
||||
|
||||
// 检查与所有已有标签是否碰撞
|
||||
let colliding = false;
|
||||
for (let i = 0; i < allTags.length; i++) {
|
||||
if (isColliding(newTag, allTags[i])) {
|
||||
colliding = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!colliding) {
|
||||
return { x, y };
|
||||
}
|
||||
}
|
||||
|
||||
// 如果网格分布尝试失败,回退到随机位置搜索
|
||||
while (attempts < maxAttempts) {
|
||||
attempts++;
|
||||
|
||||
// 随机生成一个位置
|
||||
const x = Math.random() * (containerWidth - tagWidth);
|
||||
const y = Math.random() * (containerHeight - tagHeight);
|
||||
const newTag = { x, y, width: tagWidth, height: tagHeight };
|
||||
|
||||
// 检查与所有已有标签是否碰撞
|
||||
let colliding = false;
|
||||
for (let i = 0; i < allTags.length; i++) {
|
||||
if (isColliding(newTag, allTags[i])) {
|
||||
colliding = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!colliding) {
|
||||
return { x, y };
|
||||
}
|
||||
}
|
||||
|
||||
// 如果尝试次数用完仍然找不到完全不碰撞的位置,返回随机位置
|
||||
return {
|
||||
x: Math.random() * (containerWidth - tagWidth),
|
||||
y: Math.random() * (containerHeight - tagHeight),
|
||||
};
|
||||
}
|
||||
|
||||
// 创建标签并添加动画
|
||||
function createTags() {
|
||||
const { containerWidth, containerHeight } = getContainerDimensions();
|
||||
const totalTags = tags.length;
|
||||
|
||||
// 清空容器
|
||||
container.innerHTML = "";
|
||||
allTags.length = 0;
|
||||
tagSizes.length = 0;
|
||||
|
||||
tags.forEach((tagText, index) => {
|
||||
// 创建标签元素
|
||||
const tag = document.createElement("div");
|
||||
tag.className = "bubble-tag";
|
||||
tag.textContent = tagText;
|
||||
|
||||
// 判断是否为空白标签
|
||||
const isEmpty = tagText === "";
|
||||
|
||||
// 获取标签的实际尺寸
|
||||
const { width, height } = getTagDimensions(tagText);
|
||||
tagSizes.push({ width, height });
|
||||
|
||||
// 查找不碰撞的位置
|
||||
const { x, y } = findNonCollidingPosition(width, height, containerWidth, containerHeight, totalTags, index);
|
||||
tag.style.left = `${x}px`;
|
||||
tag.style.top = `${y}px`;
|
||||
|
||||
// 保存标签信息
|
||||
const tagInfo = { x, y, width, height, element: tag };
|
||||
|
||||
// 如果是空白标签,添加标记并设置不同的样式
|
||||
if (isEmpty) {
|
||||
tagInfo.isEmpty = true;
|
||||
tag.style.backgroundColor = "transparent";
|
||||
tag.style.zIndex = "0";
|
||||
tag.style.border = "1px dashed rgba(150, 150, 255, 0.5)";
|
||||
tag.style.pointerEvents = "none";
|
||||
emptyTags.push(tagInfo);
|
||||
} else {
|
||||
tag.style.zIndex = "1";
|
||||
}
|
||||
|
||||
allTags.push(tagInfo);
|
||||
|
||||
// 随机大小
|
||||
const randomSize = 0.9 + Math.random() * 0.4; // 稍微增大了最小尺寸
|
||||
tag.style.transform = `scale(${randomSize})`;
|
||||
|
||||
// 随机动画延迟
|
||||
const delay = Math.random() * 5;
|
||||
tag.style.animationDelay = `${delay}s`;
|
||||
|
||||
// 随机动画持续时间
|
||||
const duration = 10 + Math.random() * 10; // 延长动画周期,使效果更流畅
|
||||
tag.style.animation = `float ${duration}s infinite ease-in-out, pulse 3s infinite alternate`;
|
||||
|
||||
// 非空白标签设置背景色
|
||||
if (!isEmpty) {
|
||||
// 随机背景色 - 模仿截图中的颜色范围
|
||||
const hue = 250 + Math.random() * 60; // 紫色到粉色的范围
|
||||
const lightness = 85 + Math.random() * 10; // 亮度调整
|
||||
tag.style.backgroundColor = `hsla(${hue}, 70%, ${lightness}%, 0.9)`;
|
||||
}
|
||||
|
||||
// 添加到容器
|
||||
container.appendChild(tag);
|
||||
|
||||
// 非空白标签添加点击效果
|
||||
if (!isEmpty) {
|
||||
tag.addEventListener("click", () => {
|
||||
tag.style.animation = "none";
|
||||
tag.style.transform = "scale(1.3)";
|
||||
tag.style.zIndex = "10";
|
||||
setTimeout(() => {
|
||||
tag.style.animation = `float ${duration}s infinite ease-in-out, pulse 3s infinite alternate`;
|
||||
tag.style.animationDelay = "0s";
|
||||
tag.style.zIndex = "1";
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 窗口大小变化时重新创建标签
|
||||
window.addEventListener("resize", () => {
|
||||
createTags();
|
||||
});
|
||||
|
||||
// 初始创建标签
|
||||
createTags();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
290
static/css/song-request-station.css
Normal file
@@ -0,0 +1,290 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
background-color: #333333;
|
||||
/* 气泡浮动动画 */
|
||||
/* 呼吸效果动画 */
|
||||
}
|
||||
.container .flexflex {
|
||||
display: flex;
|
||||
}
|
||||
.container .flexcenter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.container .flexjcenter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.container .flexacenter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.container .flex1 {
|
||||
flex: 1;
|
||||
}
|
||||
.container .flexcolumn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.container .mar1200 {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.container * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.container body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.container .bubble-tag {
|
||||
position: absolute;
|
||||
padding: 8px 18px;
|
||||
border-radius: 25px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .bubble-tag:hover {
|
||||
transform: scale(1.15);
|
||||
z-index: 10;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) translateX(0) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-15px) translateX(10px) rotate(1deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-30px) translateX(5px) rotate(0deg);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-15px) translateX(-10px) rotate(-1deg);
|
||||
}
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.8;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
.container .container-box {
|
||||
padding-top: 24px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.container .container-box .logo {
|
||||
width: 121px;
|
||||
height: 24px;
|
||||
margin-bottom: 31px;
|
||||
}
|
||||
.container .container-box .header {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
height: 320px;
|
||||
border-radius: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.container .container-box .header::after {
|
||||
content: "";
|
||||
width: 1200px;
|
||||
height: 320px;
|
||||
background: linear-gradient(180deg, #7d4bf8 0%, #5241b0 100%);
|
||||
border-radius: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.container .container-box .header .halo {
|
||||
width: 240px;
|
||||
height: 214px;
|
||||
position: absolute;
|
||||
top: -71px;
|
||||
left: -59px;
|
||||
}
|
||||
.container .container-box .header .star-icon {
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: 51px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .love-little {
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: 51px;
|
||||
width: 186px;
|
||||
height: 160px;
|
||||
transform: rotate(315deg);
|
||||
top: 137px;
|
||||
left: -10px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .bj-2 {
|
||||
width: 360px;
|
||||
height: 128px;
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .music-icon {
|
||||
width: 34px;
|
||||
height: 37px;
|
||||
position: absolute;
|
||||
top: 194px;
|
||||
left: 40px;
|
||||
transform: rotate(345deg);
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .bj {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 1064px;
|
||||
height: 320px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .love-big {
|
||||
transform: rotate(44deg);
|
||||
width: 321px;
|
||||
height: 276px;
|
||||
position: absolute;
|
||||
top: 31px;
|
||||
left: 69px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .music-score {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 217px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .robot {
|
||||
width: 238px;
|
||||
height: 238px;
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
left: 432px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .text {
|
||||
width: 224px;
|
||||
height: 194px;
|
||||
position: absolute;
|
||||
top: 68px;
|
||||
left: 694px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .face {
|
||||
width: 83px;
|
||||
height: 83px;
|
||||
position: absolute;
|
||||
top: -27px;
|
||||
left: 1080px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .star-icon-2 {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
position: absolute;
|
||||
left: 953px;
|
||||
top: 70px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .ai-music {
|
||||
width: 81px;
|
||||
height: 28px;
|
||||
position: absolute;
|
||||
top: 282px;
|
||||
left: 1106px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .record-black {
|
||||
width: 223px;
|
||||
height: 233px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -1px;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .record-circle {
|
||||
position: absolute;
|
||||
top: 147px;
|
||||
left: 1039px;
|
||||
background-color: #72db86;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
}
|
||||
.container .container-box .header .record-circle::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
.container .container-box .header .green-glow {
|
||||
width: 306px;
|
||||
height: 62px;
|
||||
position: absolute;
|
||||
top: 285px;
|
||||
left: 217px;
|
||||
}
|
||||
.container .container-box .header .shadow {
|
||||
width: 240px;
|
||||
height: 165px;
|
||||
position: absolute;
|
||||
top: 191px;
|
||||
left: 997px;
|
||||
}
|
||||
.container .container-box .list-box {
|
||||
width: 1200px;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.10980392);
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 3px 0;
|
||||
margin-bottom: 40px;
|
||||
flex: 1;
|
||||
}
|
||||
.container .container-box .list-box .list {
|
||||
width: 1194px;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, #7d4bf8 0%, #5241b0 100%);
|
||||
border-radius: 18px;
|
||||
}
|
||||
324
static/css/song-request-station.less
Normal file
@@ -0,0 +1,324 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
background-color: rgba(51, 51, 51, 1);
|
||||
|
||||
.flexflex {
|
||||
display: flex;
|
||||
}
|
||||
.flexcenter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.flexjcenter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.flexacenter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.flex1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flexcolumn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mar1200 {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.bubble-tag {
|
||||
position: absolute;
|
||||
padding: 8px 18px;
|
||||
border-radius: 25px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bubble-tag:hover {
|
||||
transform: scale(1.15);
|
||||
z-index: 10;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 气泡浮动动画 */
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) translateX(0) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-15px) translateX(10px) rotate(1deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-30px) translateX(5px) rotate(0deg);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-15px) translateX(-10px) rotate(-1deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 呼吸效果动画 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.8;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.container-box {
|
||||
padding-top: 24px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.logo {
|
||||
width: 121px;
|
||||
height: 24px;
|
||||
margin-bottom: 31px;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
height: 320px;
|
||||
// background: linear-gradient(180deg, #7d4bf8 0%, #5241b0 100%);
|
||||
border-radius: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
width: 1200px;
|
||||
height: 320px;
|
||||
background: linear-gradient(180deg, #7d4bf8 0%, #5241b0 100%);
|
||||
border-radius: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.halo {
|
||||
width: 240px;
|
||||
height: 214px;
|
||||
position: absolute;
|
||||
top: -71px;
|
||||
left: -59px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: 51px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.love-little {
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: 51px;
|
||||
width: 186px;
|
||||
height: 160px;
|
||||
transform: rotate(315deg);
|
||||
top: 137px;
|
||||
left: -10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bj-2 {
|
||||
width: 360px;
|
||||
height: 128px;
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.music-icon {
|
||||
width: 34px;
|
||||
height: 37px;
|
||||
position: absolute;
|
||||
top: 194px;
|
||||
left: 40px;
|
||||
transform: rotate(345deg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bj {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 1064px;
|
||||
height: 320px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.love-big {
|
||||
transform: rotate(44deg);
|
||||
width: 321px;
|
||||
height: 276px;
|
||||
position: absolute;
|
||||
top: 31px;
|
||||
left: 69px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.music-score {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 217px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.robot {
|
||||
width: 238px;
|
||||
height: 238px;
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
left: 432px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
width: 224px;
|
||||
height: 194px;
|
||||
position: absolute;
|
||||
top: 68px;
|
||||
left: 694px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.face {
|
||||
width: 83px;
|
||||
height: 83px;
|
||||
position: absolute;
|
||||
top: -27px;
|
||||
left: 1080px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.star-icon-2 {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
position: absolute;
|
||||
left: 953px;
|
||||
top: 70px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ai-music {
|
||||
width: 81px;
|
||||
height: 28px;
|
||||
position: absolute;
|
||||
top: 282px;
|
||||
left: 1106px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.record-black {
|
||||
width: 223px;
|
||||
height: 233px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.record-circle {
|
||||
position: absolute;
|
||||
top: 147px;
|
||||
left: 1039px;
|
||||
background-color: rgb(114, 219, 134);
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.green-glow {
|
||||
width: 306px;
|
||||
height: 62px;
|
||||
position: absolute;
|
||||
top: 285px;
|
||||
left: 217px;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
width: 240px;
|
||||
height: 165px;
|
||||
position: absolute;
|
||||
top: 191px;
|
||||
left: 997px;
|
||||
}
|
||||
}
|
||||
|
||||
.list-box {
|
||||
width: 1200px;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.109803921568627);
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 3px 0;
|
||||
margin-bottom: 40px;
|
||||
flex: 1;
|
||||
.list {
|
||||
width: 1194px;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, #7d4bf8 0%, #5241b0 100%);
|
||||
border-radius: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
static/img/ai-music.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 66 KiB |
BIN
static/img/green-glow.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
static/img/halo.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
12
static/img/love-big.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="321px" height="276px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient gradientUnits="userSpaceOnUse" x1="80.0208971180524" y1="201.54595377948" x2="297.940183742093" y2="22.7556431456892" id="LinearGradient83">
|
||||
<stop id="Stop84" stop-color="#ff8eba" offset="0" />
|
||||
<stop id="Stop85" stop-color="#f4458c" offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="matrix(1 0 0 1 -429 -110 )">
|
||||
<path d="M 168.381696428571 272.765625 C 166.232142857143 274.921875 163.604910714286 276 160.5 276 C 157.395089285714 276 154.767857142857 274.921875 152.618303571429 272.765625 L 40.8415178571429 164.59375 C 39.6473214285714 163.635416666667 38.0053013392857 162.078125 35.9154575892857 159.921875 C 33.8256138392857 157.765625 30.51171875 153.842447916667 25.9737723214286 148.15234375 C 21.4358258928571 142.462239583333 17.3755580357143 136.622395833333 13.79296875 130.6328125 C 10.2103794642857 124.643229166667 7.01590401785714 117.395833333333 4.20954241071429 108.890625 C 1.40318080357143 100.385416666667 0 92.1197916666666 0 84.09375 C 0 57.7395833333333 7.58314732142857 37.1354166666667 22.7494419642857 22.28125 C 37.9157366071429 7.42708333333333 58.8738839285714 0 85.6238839285714 0 C 93.0279017857143 0 100.581194196429 1.28776041666665 108.283761160714 3.86328125000001 C 115.986328125 6.43880208333331 123.151506696429 9.91276041666665 129.779296875 14.28515625 C 136.407087053571 18.6575520833333 142.109375 22.7604166666666 146.886160714286 26.59375 C 151.662946428571 30.4270833333333 156.200892857143 34.5 160.5 38.8125 C 164.799107142857 34.5 169.337053571429 30.4270833333333 174.113839285714 26.59375 C 178.890625 22.7604166666666 184.592912946429 18.6575520833333 191.220703125 14.28515625 C 197.848493303571 9.91276041666665 205.013671875 6.43880208333331 212.716238839286 3.86328125000001 C 220.418805803571 1.28776041666665 227.972098214286 0 235.376116071429 0 C 262.126116071429 0 283.084263392857 7.42708333333333 298.250558035714 22.28125 C 313.416852678571 37.1354166666667 321 57.7395833333333 321 84.09375 C 321 110.567708333333 307.326450892857 137.520833333333 279.979352678571 164.953125 L 168.381696428571 272.765625 Z " fill-rule="nonzero" fill="url(#LinearGradient83)" stroke="none" transform="matrix(1 0 0 1 429 110 )" />
|
||||
</g>
|
||||
</svg>
|
||||
12
static/img/love-little.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="186px" height="160px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient gradientUnits="userSpaceOnUse" x1="49.0234708755539" y1="118.474345856316" x2="180.341391096329" y2="24.280835020015" id="LinearGradient77">
|
||||
<stop id="Stop78" stop-color="#a943e4" offset="0" />
|
||||
<stop id="Stop79" stop-color="#f4458c" offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="matrix(1 0 0 1 -350 -216 )">
|
||||
<path d="M 97.5669642857143 158.125 C 96.3214285714286 159.375 94.7991071428572 160 93 160 C 91.2008928571429 160 89.6785714285714 159.375 88.4330357142857 158.125 L 23.6651785714286 95.4166666666667 C 22.9732142857143 94.8611111111111 22.0217633928571 93.9583333333333 20.8108258928571 92.7083333333333 C 19.5998883928571 91.4583333333333 17.6796875 89.1840277777778 15.0502232142857 85.8854166666667 C 12.4207589285714 82.5868055555555 10.0680803571429 79.2013888888889 7.9921875 75.7291666666667 C 5.91629464285714 72.2569444444444 4.06529017857143 68.0555555555556 2.43917410714286 63.125 C 0.813058035714286 58.1944444444444 0 53.4027777777778 0 48.75 C 0 33.4722222222222 4.39397321428571 21.5277777777778 13.1819196428571 12.9166666666667 C 21.9698660714286 4.30555555555555 34.1138392857143 0 49.6138392857143 0 C 53.9040178571429 0 58.2806919642857 0.746527777777768 62.7438616071429 2.23958333333334 C 67.20703125 3.73263888888888 71.3588169642857 5.74652777777777 75.19921875 8.28125 C 79.0396205357143 10.8159722222222 82.34375 13.1944444444444 85.1116071428571 15.4166666666667 C 87.8794642857143 17.6388888888889 90.5089285714286 20 93 22.5 C 95.4910714285714 20 98.1205357142857 17.6388888888889 100.888392857143 15.4166666666667 C 103.65625 13.1944444444444 106.960379464286 10.8159722222222 110.80078125 8.28125 C 114.641183035714 5.74652777777777 118.79296875 3.73263888888888 123.256138392857 2.23958333333334 C 127.719308035714 0.746527777777768 132.095982142857 0 136.386160714286 0 C 151.886160714286 0 164.030133928571 4.30555555555555 172.818080357143 12.9166666666667 C 181.606026785714 21.5277777777778 186 33.4722222222222 186 48.75 C 186 64.0972222222222 178.077008928571 79.7222222222222 162.231026785714 95.625 L 97.5669642857143 158.125 Z " fill-rule="nonzero" fill="url(#LinearGradient77)" stroke="none" transform="matrix(1 0 0 1 350 216 )" />
|
||||
</g>
|
||||
</svg>
|
||||
12
static/img/music-icon.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="34px" height="37px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient gradientUnits="userSpaceOnUse" x1="17" y1="0" x2="17" y2="37" id="LinearGradient86">
|
||||
<stop id="Stop87" stop-color="#fddf6d" offset="0" />
|
||||
<stop id="Stop88" stop-color="#f3974a" offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="matrix(1 0 0 1 -400 -273 )">
|
||||
<path d="M 33.3802083333333 0.622596153846156 C 33.7934027777778 1.03766025641025 34 1.54166666666667 34 2.13461538461539 L 34 27.0384615384615 C 34 27.7796474358974 33.7491319444444 28.4393028846154 33.2473958333333 29.0174278846154 C 32.7456597222222 29.5955528846154 32.1111111111111 30.0439703525641 31.34375 30.3626802884615 C 30.5763888888889 30.681390224359 29.8127170138889 30.9185697115385 29.052734375 31.07421875 C 28.2927517361111 31.2298677884615 27.5807291666667 31.3076923076923 26.9166666666667 31.3076923076923 C 26.2526041666667 31.3076923076923 25.5405815972222 31.2298677884615 24.7805989583333 31.07421875 C 24.0206163194444 30.9185697115385 23.2569444444444 30.681390224359 22.4895833333333 30.3626802884615 C 21.7222222222222 30.0439703525641 21.0876736111111 29.5955528846154 20.5859375 29.0174278846154 C 20.0842013888889 28.4393028846154 19.8333333333333 27.7796474358974 19.8333333333333 27.0384615384615 C 19.8333333333333 26.2972756410256 20.0842013888889 25.6376201923077 20.5859375 25.0594951923077 C 21.0876736111111 24.4813701923077 21.7222222222222 24.032952724359 22.4895833333333 23.7142427884615 C 23.2569444444444 23.3955328525641 24.0206163194444 23.1583533653846 24.7805989583333 23.0027043269231 C 25.5405815972222 22.8470552884615 26.2526041666667 22.7692307692308 26.9166666666667 22.7692307692308 C 28.4661458333333 22.7692307692308 29.8828125 23.0582932692308 31 23.6364182692308 L 31 11.6959134615385 L 14.1666666666667 16.9657451923077 L 14.1666666666667 32.7307692307692 C 14.1666666666667 33.4719551282051 13.9157986111111 34.1316105769231 13.4140625 34.7097355769231 C 12.9123263888889 35.2878605769231 12.2777777777778 35.7362780448718 11.5104166666667 36.0549879807692 C 10.7430555555556 36.3736979166667 9.97938368055556 36.6108774038462 9.21940104166667 36.7665264423077 C 8.45941840277778 36.9221754807692 7.74739583333333 37 7.08333333333333 37 C 6.41927083333333 37 5.70724826388889 36.9221754807692 4.947265625 36.7665264423077 C 4.18728298611111 36.6108774038462 3.42361111111111 36.3736979166667 2.65625 36.0549879807692 C 1.88888888888889 35.7362780448718 1.25434027777778 35.2878605769231 0.752604166666667 34.7097355769231 C 0.250868055555556 34.1316105769231 0 33.4719551282051 0 32.7307692307692 C 0 31.9895833333333 0.250868055555556 31.3299278846154 0.752604166666667 30.7518028846154 C 1.25434027777778 30.1736778846154 1.88888888888889 29.7252604166667 2.65625 29.4065504807692 C 3.42361111111111 29.0878405448718 4.18728298611111 28.8506610576923 4.947265625 28.6950120192308 C 5.70724826388889 28.5393629807692 6.41927083333333 28.4615384615385 7.08333333333333 28.4615384615385 C 8.6328125 28.4615384615385 10.0494791666667 28.7506009615385 12 29.3287259615385 L 12 7.82692307692308 C 11.3333333333333 7.36738782051282 11.4735243055556 6.94861778846154 11.75390625 6.57061298076923 C 12.0342881944444 6.19260817307692 12.3958333333333 5.92948717948718 12.8385416666667 5.78125 L 31.2552083333333 0.0889423076923074 C 31.4322916666667 0.0296474358974358 31.6388888888889 0 31.875 0 C 32.4652777777778 0 32.9670138888889 0.207532051282051 33.3802083333333 0.622596153846156 Z " fill-rule="nonzero" fill="url(#LinearGradient86)" stroke="none" transform="matrix(1 0 0 1 400 273 )" />
|
||||
</g>
|
||||
</svg>
|
||||
BIN
static/img/music-score.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
6
static/img/record-black.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="223px" height="233px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1 0 0 1 -1338 -166 )">
|
||||
<path d="M 116.443349753695 0 C 164.076564175014 0 204.253703364059 27.3522430445635 222 67.5256649406986 L 222 165.474335053222 C 204.253703364059 205.647756955436 164.076564175014 233 116.443349753695 233 C 51.2350738916256 233 0 181.74 0 116.5 C 0 51.26 51.2350738916256 0 116.443349753695 0 Z " fill-rule="nonzero" fill="#000000" stroke="none" transform="matrix(1 0 0 1 1338 166 )" />
|
||||
</g>
|
||||
</svg>
|
||||
BIN
static/img/robot.png
Normal file
|
After Width: | Height: | Size: 358 KiB |
BIN
static/img/shadow.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
static/img/smiling-face.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
12
static/img/song-request-bj-2.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="360px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient gradientUnits="userSpaceOnUse" x1="130.108695652174" y1="128" x2="259.239130434783" y2="79.0588235294117" id="LinearGradient74">
|
||||
<stop id="Stop75" stop-color="#2f1071" offset="0" />
|
||||
<stop id="Stop76" stop-color="#611ccf" offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="matrix(1 0 0 1 -385 -271 )">
|
||||
<path d="M 177 128 L 0 83.6535433070867 L 347 0 L 360 128 L 177 128 Z " fill-rule="nonzero" fill="url(#LinearGradient74)" stroke="none" transform="matrix(1 0 0 1 385 271 )" />
|
||||
</g>
|
||||
</svg>
|
||||
12
static/img/song-request-bj.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1064px" height="320px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient gradientUnits="userSpaceOnUse" x1="31.9200000000012" y1="16.9411764705882" x2="1028.53333333334" y2="310.588235294118" id="LinearGradient80">
|
||||
<stop id="Stop81" stop-color="#c22cc3" offset="0" />
|
||||
<stop id="Stop82" stop-color="#db2cd9" offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="matrix(1 0 0 1 -496 -79 )">
|
||||
<path d="M 184 320 L 0 271 L 111 0 L 1064 247 L 1064 300 C 1064 311.2 1055.2 320 1044 320 L 184 320 Z " fill-rule="nonzero" fill="url(#LinearGradient80)" stroke="none" transform="matrix(1 0 0 1 496 79 )" />
|
||||
</g>
|
||||
</svg>
|
||||
18
static/img/song-request-text.svg
Normal file
BIN
static/img/star-icon-2.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
static/img/star-icon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -179,6 +179,8 @@ const search = createApp({
|
||||
let art = null;
|
||||
// 开启播放 MV
|
||||
const openPreview = (id) => {
|
||||
closeAll();
|
||||
|
||||
ajax("https://pujianchaoyin.com/api/getMvDetail", {
|
||||
id,
|
||||
}).then((res) => {
|
||||
@@ -234,10 +236,8 @@ const search = createApp({
|
||||
const manageAudio = (src, area) => {
|
||||
const audio = audioPlayer.value;
|
||||
closeAll();
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if (audio?.src != src) audio.src = src;
|
||||
console.log("audio", audio);
|
||||
|
||||
audio.play().then(() => {
|
||||
if (area == "works") {
|
||||
awardAudioList.value.forEach((item) => {
|
||||
@@ -262,7 +262,7 @@ const search = createApp({
|
||||
playData.value = { ...zeroOrderStudents.value, area };
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// 重新播放
|
||||
|
||||
151
tag-bubbles copy.html
Normal file
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>标签气泡动效</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.bubble-tag {
|
||||
position: absolute;
|
||||
padding: 8px 18px;
|
||||
border-radius: 25px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bubble-tag:hover {
|
||||
transform: scale(1.15);
|
||||
z-index: 10;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 气泡浮动动画 */
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0) translateX(0) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-15px) translateX(10px) rotate(1deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-30px) translateX(5px) rotate(0deg);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-15px) translateX(-10px) rotate(-1deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 呼吸效果动画 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.8;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="title">标签气泡动效演示</h1>
|
||||
<div class="container" id="bubbleContainer"></div>
|
||||
|
||||
<script>
|
||||
// 标签数据
|
||||
const tags = [
|
||||
"前端开发", "JavaScript", "CSS动画", "HTML5",
|
||||
"React", "Vue", "TypeScript", "Node.js",
|
||||
"UI设计", "用户体验", "响应式布局", "性能优化",
|
||||
"微信小程序", "PWA", "Canvas", "SVG",
|
||||
"WebGL", "数据可视化", "模块化", "组件化"
|
||||
];
|
||||
|
||||
// 获取容器
|
||||
const container = document.getElementById('bubbleContainer');
|
||||
const containerWidth = container.offsetWidth;
|
||||
const containerHeight = container.offsetHeight;
|
||||
|
||||
// 创建标签并添加动画
|
||||
tags.forEach((tagText, index) => {
|
||||
// 创建标签元素
|
||||
const tag = document.createElement('div');
|
||||
tag.className = 'bubble-tag';
|
||||
tag.textContent = tagText;
|
||||
|
||||
// 随机位置
|
||||
const randomX = Math.random() * (containerWidth - 120);
|
||||
const randomY = Math.random() * (containerHeight - 50);
|
||||
tag.style.left = `${randomX}px`;
|
||||
tag.style.top = `${randomY}px`;
|
||||
|
||||
// 随机大小
|
||||
const randomSize = 0.8 + Math.random() * 0.4;
|
||||
tag.style.transform = `scale(${randomSize})`;
|
||||
|
||||
// 随机动画延迟
|
||||
const delay = Math.random() * 5;
|
||||
tag.style.animationDelay = `${delay}s`;
|
||||
|
||||
// 随机动画持续时间
|
||||
const duration = 8 + Math.random() * 8;
|
||||
tag.style.animation = `float ${duration}s infinite ease-in-out, pulse 3s infinite alternate`;
|
||||
|
||||
// 随机背景色
|
||||
const hue = 260 + Math.random() * 40; // 紫色调范围
|
||||
const lightness = 90 + Math.random() * 5;
|
||||
tag.style.backgroundColor = `hsla(${hue}, 70%, ${lightness}%, 0.9)`;
|
||||
|
||||
// 添加到容器
|
||||
container.appendChild(tag);
|
||||
|
||||
// 添加点击效果
|
||||
tag.addEventListener('click', () => {
|
||||
tag.style.animation = 'none';
|
||||
tag.style.transform = 'scale(1.3)';
|
||||
setTimeout(() => {
|
||||
tag.style.animation = `float ${duration}s infinite ease-in-out, pulse 3s infinite alternate`;
|
||||
tag.style.animationDelay = '0s';
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||