no message

This commit is contained in:
DESKTOP-RQ919RC\Pc
2025-09-19 18:43:17 +08:00
parent 8542840577
commit 7355bdd146
9 changed files with 1427 additions and 235 deletions

View File

@@ -7,9 +7,9 @@
<style></style>
<link rel="stylesheet" href="./static/css/song-request-station.css" />
<script src="./static/js/tagcloud-2.2.js"></script>
</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="" />
@@ -33,6 +33,9 @@
<img class="shadow" src="./static/img/shadow.png" />
</div>
<div class="list-box">
<img class="left-icon" src="./static/img/left-icon.png" alt="" />
<img class="right-icon" src="./static/img/right-icon.png" alt="" />
<div class="list-fill" id="bubbleContainerFill"></div>
<div class="list" id="bubbleContainer"></div>
</div>
</div>
@@ -40,19 +43,12 @@
<script>
// 标签数据
const tags = ["前端开发", "JavaScript", "CSS动画", "HTML5", "React", "Vue", "TypeScript", "Node.js", "UI设计", "用户体验", "响应式布局", "性能优化", "微信小程序", "PWA", "Canvas", "SVG", "WebGL", "数据可视化", "模块化", "组件化", "", "", "", "", ""];
// let tags = ["前端开发", "JavaScript", "CSS动画", "HTML5", "React", "Vue", "TypeScript", "Node.js", "UI设计", "用户体验", "响应式布局", "性能优化", "微信小程序", "PWA", "Canvas", "SVG", "WebGL", "数据可视化", "模块化", "组件化", "前端开发", "JavaScript", "CSS动画", "HTML5", "React", "Vue", "TypeScript", "Node.js", "UI设计", "用户体验", "响应式布局", "性能优化", "微信小程序", "PWA", "Canvas", "SVG", "WebGL", "数据可视化", "模块化", "组件化"];
let tags = [];
// 获取容器
const containerFill = document.getElementById("bubbleContainerFill");
const container = document.getElementById("bubbleContainer");
// 创建空白标签数组,用于存储所有空白标签的信息
const emptyTags = [];
// 存储所有标签的位置和大小信息
const allTags = [];
const tagSizes = []; // 存储每个标签的实际尺寸
const defaultTagWidth = 120; // 默认标签宽度
const defaultTagHeight = 40; // 默认标签高度
// 计算容器尺寸
function getContainerDimensions() {
@@ -61,227 +57,122 @@
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 fillLength = tags.length + tags.length / 2;
for (let i = 0; i < fillLength; i++) {
const outcome = getRandomOutcome();
// 创建标签元素
const tag = document.createElement("div");
tag.className = "bubble-tag";
tag.textContent = tagText;
// 判断是否为空白标签
const isEmpty = tagText === "";
// 获取标签的实际尺寸
const { width, height } = getTagDimensions(tagText);
tagSizes.push({ width, height });
tag.className = `fill-item item${Math.floor(Math.random() * 5) + 1}`;
// 查找不碰撞的位置
const { x, y } = findNonCollidingPosition(width, height, containerWidth, containerHeight, totalTags, index);
tag.style.left = `${x}px`;
tag.style.top = `${y}px`;
tag.style.width = `${Math.floor(Math.random() * 77) + 33}`;
tag.style.height = `${Math.floor(Math.random() * 15) + 23}`;
containerFill.appendChild(tag);
}
// 保存标签信息
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);
tagCloud({
selector: "#bubbleContainerFill", // 元素选择器id 或 class
// fontsize: 20, // 基本字体大小, 默认16单位px
radius: [containerWidth / 2, containerHeight / 2], // 滚动横/纵轴半径, 默认60单位px取值60[60][60, 60]
mspeed: "slow", // 滚动最大速度, 取值: slow, normal(默认), fast
ispeed: "slow", // 滚动初速度, 取值: slow, normal(默认), fast
direction: 45, // 初始滚动方向, 取值角度(顺时针360): 0对应top, 90对应left, 135对应right-bottom(默认)...
keep: false, // 鼠标移出组件后是否继续随鼠标滚动, 取值: false, true(默认) 对应 减速至初速度滚动, 随鼠标滚动
multicolour: false, // 彩色字体颜色随机取值true(默认),false
});
// 随机大小
const randomSize = 0.9 + Math.random() * 0.4; // 稍微增大了最小尺寸
tag.style.transform = `scale(${randomSize})`;
let redIndex = 0;
let redAmount = 5;
// 随机动画延迟
const delay = Math.random() * 5;
tag.style.animationDelay = `${delay}s`;
const redCount = Math.min(5, tags.length);
const redIndexes = [];
// 随机动画持续时间
const duration = 10 + Math.random() * 10; // 延长动画周期,使效果更流畅
tag.style.animation = `float ${duration}s infinite ease-in-out, pulse 3s infinite alternate`;
while (redIndexes.length < redCount) {
const randomIdx = Math.floor(Math.random() * tags.length);
if (!redIndexes.includes(randomIdx)) redIndexes.push(randomIdx);
}
console.log("redIndexes", redIndexes);
// 非空白标签设置背景色
if (!isEmpty) {
// 随机背景色 - 模仿截图中的颜色范围
const hue = 250 + Math.random() * 60; // 紫色到粉色的范围
const lightness = 85 + Math.random() * 10; // 亮度调整
tag.style.backgroundColor = `hsla(${hue}, 70%, ${lightness}%, 0.9)`;
}
const surplus = Math.floor(tags.length % redAmount); // 向下取整
tags.forEach((item, index) => {
const outcome = getRandomOutcome();
// 创建标签元素
const tag = document.createElement("div");
tag.className = `tag-item item${outcome}`;
tag.textContent = item.tag;
// 关键判断:当前索引是否在“要加 red 的索引列表”中
if (redIndexes.includes(index)) tag.classList.add("red");
// 绑定点击事件
tag.addEventListener("click", () => {
const songs = item.songs || [];
console.log("songs", songs);
});
// 添加到容器
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);
});
}
// 3D云标签
tagCloud({
selector: "#bubbleContainer", // 元素选择器id 或 class
// fontsize: 20, // 基本字体大小, 默认16单位px
radius: [containerWidth / 2, containerHeight / 2], // 滚动横/纵轴半径, 默认60单位px取值60[60][60, 60]
mspeed: "slow", // 滚动最大速度, 取值: slow, normal(默认), fast
ispeed: "normal", // 滚动初速度, 取值: slow, normal(默认), fast
direction: 135, // 初始滚动方向, 取值角度(顺时针360): 0对应top, 90对应left, 135对应right-bottom(默认)...
keep: false, // 鼠标移出组件后是否继续随鼠标滚动, 取值: false, true(默认) 对应 减速至初速度滚动, 随鼠标滚动
multicolour: false, // 彩色字体颜色随机取值true(默认),false
});
}
// 窗口大小变化时重新创建标签
window.addEventListener("resize", () => {
createTags();
});
// // 窗口大小变化时重新创建标签
// window.addEventListener("resize", () => {
// createTags();
// });
// 初始创建标签
createTags();
const init = () => {
// 发送请求读取本地 data.json
fetch("./static/js/music.json")
.then((response) => {
// 检查请求是否成功(状态码 200-299
if (!response.ok) {
throw new Error(`请求失败:${response.status}`);
}
// 将响应解析为 JSON 格式
return response.json();
})
.then((data) => {
// 成功获取 JSON 数据,后续处理(如使用 tags 数组)
console.log("读取到的 JSON 数据:", data);
const tagsData = data.data; // 提取 tags 数组
console.log("tags 数组:", tagsData); // 输出:["HTML", "CSS", "JavaScript", "Vue", "React"]
tags = tagsData;
createTags();
// 这里可以继续你的标签生成逻辑(如之前的 tag.forEach...
});
// createTags();
};
init();
// 生成单次随机结果的函数
function getRandomOutcome() {
const random = Math.random(); // 生成0-1之间的随机数
let cumulativeProbability = 0;
const outcomes = [0.1, 0.2, 0.3, 0.4];
for (const outcome of outcomes) {
cumulativeProbability += outcome;
if (random < cumulativeProbability) {
return outcomes.indexOf(outcome) + 1;
}
}
}
</script>
</body>
</html>