refactor(页面布局): 重构页面布局和样式
- 移除未使用的HTML文件和冗余代码 - 调整侧边栏位置和样式 - 优化标签云组件交互和性能 - 更新播放器控件样式和功能 - 改进预览弹窗的背景透明度 - 添加favicon图标
This commit is contained in:
117
2.html
117
2.html
@@ -1,117 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Document</title>
|
|
||||||
<style>
|
|
||||||
.tag-container {
|
|
||||||
position: relative; /* 作为标签绝对定位的参考容器 */
|
|
||||||
width: 800px; /* 容器宽度,可根据需求调整 */
|
|
||||||
height: 500px; /* 容器高度,可根据需求调整 */
|
|
||||||
background-color: purple; /* 容器背景,方便可视化 */
|
|
||||||
overflow: hidden; /* 防止标签超出容器(可选) */
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
position: absolute; /* 标签绝对定位,由JS控制位置 */
|
|
||||||
padding: 5px 10px; /* 内边距,美化标签 */
|
|
||||||
border-radius: 4px; /* 圆角,美化标签 */
|
|
||||||
color: white; /* 文字颜色 */
|
|
||||||
background-color: pink; /* 标签背景色,可根据需求调整 */
|
|
||||||
white-space: nowrap; /* 防止文字换行,确保宽度由文字长度决定 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 四种高度的标签(通过data-height区分) */
|
|
||||||
.tag[data-height="1"] {
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
.tag[data-height="2"] {
|
|
||||||
height: 40px;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
.tag[data-height="3"] {
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
}
|
|
||||||
.tag[data-height="4"] {
|
|
||||||
height: 60px;
|
|
||||||
line-height: 60px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="tag-container">
|
|
||||||
<div class="tag" data-height="1">生日祝福</div>
|
|
||||||
<div class="tag" data-height="2">运动健身</div>
|
|
||||||
<div class="tag" data-height="3">通勤路上</div>
|
|
||||||
<div class="tag" data-height="4">影视配乐</div>
|
|
||||||
<div class="tag" data-height="1">助眠放松</div>
|
|
||||||
<div class="tag" data-height="2">派对聚会</div>
|
|
||||||
<div class="tag" data-height="3">约会浪漫</div>
|
|
||||||
<!-- 可添加更多标签 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const container = document.querySelector(".tag-container");
|
|
||||||
const tags = document.querySelectorAll(".tag");
|
|
||||||
const containerWidth = container.offsetWidth; // 容器宽度
|
|
||||||
const containerHeight = container.offsetHeight; // 容器高度
|
|
||||||
const placedTags = []; // 存储已放置标签的位置信息:{x, y, width, height}
|
|
||||||
|
|
||||||
tags.forEach((tag) => {
|
|
||||||
// 获取标签高度(从data-height属性转换)
|
|
||||||
const height = parseInt(tag.dataset.height) * 10 + 20; // 对应30/40/50/60px
|
|
||||||
const width = tag.offsetWidth; // 标签宽度(由文字内容自动计算)
|
|
||||||
let x = 0; // 初始x坐标
|
|
||||||
let y = 0; // 初始y坐标
|
|
||||||
let isPlaced = false; // 标签是否已成功放置
|
|
||||||
|
|
||||||
while (!isPlaced) {
|
|
||||||
// 检查:若当前x + 标签宽度超出容器,换行(x重置为0,y增加当前行高度)
|
|
||||||
if (x + width > containerWidth) {
|
|
||||||
x = 0;
|
|
||||||
// 计算当前行的最大高度(用于换行时的y偏移)
|
|
||||||
let maxRowHeight = 0;
|
|
||||||
placedTags.forEach((placed) => {
|
|
||||||
if (placed.y === y) {
|
|
||||||
// 属于当前行的标签
|
|
||||||
maxRowHeight = Math.max(maxRowHeight, placed.height);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
y += maxRowHeight + 10; // 换行后,y增加“当前行最大高度 + 间距”
|
|
||||||
}
|
|
||||||
|
|
||||||
// 碰撞检测:检查当前(x,y)是否与已放置标签重叠
|
|
||||||
let isCollision = false;
|
|
||||||
for (let i = 0; i < placedTags.length; i++) {
|
|
||||||
const placed = placedTags[i];
|
|
||||||
// 矩形重叠判定:当前标签与已放置标签的矩形是否有交集
|
|
||||||
if (
|
|
||||||
x < placed.x + placed.width && // 当前标签左边界 < 已放置标签右边界
|
|
||||||
x + width > placed.x && // 当前标签右边界 > 已放置标签左边界
|
|
||||||
y < placed.y + placed.height && // 当前标签上边界 < 已放置标签下边界
|
|
||||||
y + height > placed.y // 当前标签下边界 > 已放置标签上边界
|
|
||||||
) {
|
|
||||||
isCollision = true;
|
|
||||||
// 碰撞后,将x移到“已放置标签的右边界 + 间距”,尝试下一个位置
|
|
||||||
x = placed.x + placed.width + 10;
|
|
||||||
break; // 跳出循环,重新检查新x是否合法
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无碰撞:设置标签位置,并记录到已放置数组
|
|
||||||
if (!isCollision) {
|
|
||||||
tag.style.left = `${x}px`;
|
|
||||||
tag.style.top = `${y}px`;
|
|
||||||
placedTags.push({ x, y, width, height });
|
|
||||||
isPlaced = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
33
index.html
33
index.html
@@ -21,18 +21,6 @@
|
|||||||
<audio ref="audioPlayer" preload="none" loop></audio>
|
<audio ref="audioPlayer" preload="none" loop></audio>
|
||||||
|
|
||||||
<div class="introduce" ref="introduceRef">
|
<div class="introduce" ref="introduceRef">
|
||||||
<div class="sidebar flexflex" @mouseover="changeInterval(true)" @mouseleave="changeInterval(false)">
|
|
||||||
<div class="pointer">
|
|
||||||
<div class="item" v-for="(item,index) in bannerList" :class="{'active': pointerIndex === index}" :data-index="`0${ index + 1 }`" @click="changePointer(index)"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="arrow">
|
|
||||||
<img v-if="pointerIndex != 0" class="item top orange" src="./static/img/arrow-orange.svg" @click="scrollToPrevious" />
|
|
||||||
<img v-else class="item top" src="./static/img/arrow-white.svg" />
|
|
||||||
<img v-if="pointerIndex != bannerList.length - 1" class="item bottom orange" src="./static/img/arrow-orange.svg" @click="scrollToNext" />
|
|
||||||
<img v-else class="item bottom white" src="./static/img/arrow-white.svg" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="head flexacenter">
|
<div class="head flexacenter">
|
||||||
<div class="logo mar1200">
|
<div class="logo mar1200">
|
||||||
<img class="icon" src="./static/img/logo.png" />
|
<img class="icon" src="./static/img/logo.png" />
|
||||||
@@ -49,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="award flexflex">
|
<div class="award flexflex">
|
||||||
<img class="title" src="./static/img/award-winning-works.png" />
|
<img class="title" src="./static/img/award-winning-works.png" />
|
||||||
<img class="name" v-if="bannerList[pointerIndex]?.title_pic" :src="bannerList[pointerIndex]?.title_pic" />
|
<img class="name" v-if="bannerList[pointerIndex]?.title_pic" :src="bannerList[pointerIndex]?.title_pic" :key="bannerList[pointerIndex]?.title_pic" />
|
||||||
<div class="explain">{{ bannerList[pointerIndex]?.desc }}</div>
|
<div class="explain">{{ bannerList[pointerIndex]?.desc }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,6 +54,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar flexflex" @mouseover="changeInterval(true)" @mouseleave="changeInterval(false)">
|
||||||
|
<div class="pointer">
|
||||||
|
<div class="item" v-for="(item,index) in bannerList" :class="{'active': pointerIndex === index}" :data-index="`0${ index + 1 }`" @click="changePointer(index)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="arrow">
|
||||||
|
<img v-if="pointerIndex != 0" class="item top orange" src="./static/img/arrow-orange.svg" @click="scrollToPrevious" />
|
||||||
|
<img v-else class="item top" src="./static/img/arrow-white.svg" />
|
||||||
|
<img v-if="pointerIndex != bannerList.length - 1" class="item bottom orange" src="./static/img/arrow-orange.svg" @click="scrollToNext" />
|
||||||
|
<img v-else class="item bottom white" src="./static/img/arrow-white.svg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="trait flexflex mar1200">
|
<div class="trait flexflex mar1200">
|
||||||
<div class="item flexflex" v-for="(item, index) in trait" :key="index">
|
<div class="item flexflex" v-for="(item, index) in trait" :key="index">
|
||||||
@@ -209,9 +210,9 @@
|
|||||||
<div class="text">广州九微科技有限公司 | Copyright 2001-2025 GTER All Rights Reserved | 粤ICP备14050432号</div>
|
<div class="text">广州九微科技有限公司 | Copyright 2001-2025 GTER All Rights Reserved | 粤ICP备14050432号</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="previewState" class="preview flexcenter">
|
<div v-if="previewState" class="preview flexcenter" @click="closePreview()">
|
||||||
<img class="close" @click="closePreview()" src="./static/img/close.svg" />
|
<img class="close" src="./static/img/close.svg" />
|
||||||
<div class="artplayer-app"></div>
|
<div class="artplayer-app" @click.stop=""></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-play flexacenter" v-if="playData">
|
<div class="bottom-play flexacenter" v-if="playData">
|
||||||
|
|||||||
@@ -1,256 +0,0 @@
|
|||||||
<!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>
|
|
||||||
@@ -7,10 +7,19 @@
|
|||||||
<style></style>
|
<style></style>
|
||||||
|
|
||||||
<link rel="stylesheet" href="./static/css/song-request-station.css" />
|
<link rel="stylesheet" href="./static/css/song-request-station.css" />
|
||||||
<script src="./static/js/tagcloud-2.2.js"></script>
|
<script src="./static/js/vue.global.js"></script>
|
||||||
|
<script src="./static/js/tagcloud-3.1.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
[v-cloak] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container" id="song-request-station" v-cloak>
|
||||||
|
<!-- 原生audio标签(隐藏) -->
|
||||||
|
<audio ref="audioPlayer" preload="none" loop></audio>
|
||||||
<div class="container-box mar1200">
|
<div class="container-box mar1200">
|
||||||
<img class="logo" src="./static/img/logo.png" alt="" />
|
<img class="logo" src="./static/img/logo.png" alt="" />
|
||||||
<div class="header">
|
<div class="header">
|
||||||
@@ -35,144 +44,58 @@
|
|||||||
<div class="list-box">
|
<div class="list-box">
|
||||||
<img class="left-icon" src="./static/img/left-icon.png" alt="" />
|
<img class="left-icon" src="./static/img/left-icon.png" alt="" />
|
||||||
<img class="right-icon" src="./static/img/right-icon.png" alt="" />
|
<img class="right-icon" src="./static/img/right-icon.png" alt="" />
|
||||||
<div class="list-fill" id="bubbleContainerFill"></div>
|
<div class="list-fill" id="bubbleContainerFill" ref="containerFill">
|
||||||
<div class="list" id="bubbleContainer"></div>
|
<div class="fill-item" v-for="(item, index) in tagsFill" :key="index" :class="[`item${ item.type }`]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="list" id="bubbleContainer" ref="container">
|
||||||
|
<div class="tag-item" v-for="(item, index) in tags" :key="index" :class="[`item${ item.type }`, {'red': item.isred}]" @click="clickSongs(item.songs)" @mouseleave="handleMouseleave" @mouseenter="handleMouseenter">{{ item.tag }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-play flexacenter" v-if="playData">
|
||||||
|
<div class="bottom-left flex1 flexacenter">
|
||||||
|
<img v-if="playData?.img" class="img" :src="playData?.img" />
|
||||||
|
<div class="name">{{ playData?.name }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-middle flexcenter">
|
||||||
|
<div class="operate flexcenter">
|
||||||
|
<img class="speed left" src="./static/img/speed-white-left.png" @click="fastForward('slow')" />
|
||||||
|
<img v-if="playData.state" class="play" @click="closeAll()" src="./static/img/pause-white-icon.svg" />
|
||||||
|
<img v-else class="play" @click="rePlay()" src="./static/img/play-white-icon.svg" />
|
||||||
|
<img class="speed right" src="./static/img/speed-white-right.png" @click="fastForward('fast')" />
|
||||||
|
</div>
|
||||||
|
<div class="flexacenter">
|
||||||
|
<div class="time-display">{{ currentTimeFormatted }}</div>
|
||||||
|
<div class="progress-bar flexacenter" @click="handleBarDragBottomClick">
|
||||||
|
<div class="bar white" :style="{ width: progress + '%' }" @mousedown="startBarDragBottom"></div>
|
||||||
|
<div class="bar black flex1"></div>
|
||||||
|
</div>
|
||||||
|
<div class="time-display">{{ durationFormatted }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-right flex1 flexcenter">
|
||||||
|
<div class="item" @mouseenter="handleVolumeShow" @mouseleave="handleVolumeHide">
|
||||||
|
<svg v-if="volume == 0" class="svg" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 16 16"><path fill="#FCFCFC" d="M1 6.334a1 1 0 0 1 1-1h1.385a.667.667 0 0 0 .505-.231l2.938-3.41A.667.667 0 0 1 8 2.13v11.85c0 .604-.74.896-1.154.454l-2.958-3.165a.667.667 0 0 0-.487-.211H2a1 1 0 0 1-1-1zm3.9-.36a2 2 0 0 1-1.515.694H2.333v3.055h1.068a2 2 0 0 1 1.461.635l1.805 1.93V3.924zM9.764 6.04 11.724 8l-1.96 1.96a.333.333 0 0 0 0 .471l.472.471c.13.13.341.13.471 0l1.96-1.96 1.96 1.96c.13.13.34.13.47 0l.472-.471a.333.333 0 0 0 0-.471L13.61 8l1.96-1.96a.333.333 0 0 0 0-.47l-.471-.472a.333.333 0 0 0-.472 0l-1.96 1.96-1.959-1.96a.333.333 0 0 0-.471 0l-.472.471a.333.333 0 0 0 0 .471"></path></svg>
|
||||||
|
|
||||||
|
<svg v-else class="svg" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 16 16">
|
||||||
|
<path fill="#fff" d="M1.334 6.334a1 1 0 0 1 1-1h1.385a.667.667 0 0 0 .505-.231l2.938-3.41a.667.667 0 0 1 1.172.436v11.85c0 .604-.74.896-1.154.454l-2.958-3.165a.667.667 0 0 0-.487-.211H2.334a1 1 0 0 1-1-1zm3.9-.36a2 2 0 0 1-1.515.694H2.667v3.055h1.068a2 2 0 0 1 1.461.635L7 12.288V3.924zM10.344 6.127a.364.364 0 0 1 .013-.483l.472-.472a.314.314 0 0 1 .462.009c1.374 1.601 1.374 4.038 0 5.64a.314.314 0 0 1-.462.008l-.472-.471a.364.364 0 0 1-.013-.484 3.028 3.028 0 0 0 0-3.747"></path>
|
||||||
|
<path fill="#fff" d="M12.479 12.479a.348.348 0 0 1-.007-.478c2.013-2.248 2.013-5.753 0-8.001a.348.348 0 0 1 .007-.478l.471-.471c.13-.13.342-.13.466.005 2.525 2.764 2.525 7.126 0 9.89a.322.322 0 0 1-.466.004z"></path>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="sound-control flexflex" v-if="volumeShow">
|
||||||
|
<div class="value">{{ volume }}</div>
|
||||||
|
<div class="progress flexflex" @click="handleVolumeClick">
|
||||||
|
<div class="bar" :style="{ height: volume + '%' }" @mousedown="startDrag"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script src="./static/js/song-request-station.js"></script>
|
||||||
// 标签数据
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
// 计算容器尺寸
|
|
||||||
function getContainerDimensions() {
|
|
||||||
const containerWidth = container.offsetWidth;
|
|
||||||
const containerHeight = container.offsetHeight;
|
|
||||||
return { containerWidth, containerHeight };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建标签并添加动画
|
|
||||||
function createTags() {
|
|
||||||
const { containerWidth, containerHeight } = getContainerDimensions();
|
|
||||||
|
|
||||||
const fillLength = tags.length + tags.length / 2;
|
|
||||||
for (let i = 0; i < fillLength; i++) {
|
|
||||||
const outcome = getRandomOutcome();
|
|
||||||
// 创建标签元素
|
|
||||||
const tag = document.createElement("div");
|
|
||||||
tag.className = `fill-item item${Math.floor(Math.random() * 5) + 1}`;
|
|
||||||
|
|
||||||
tag.style.width = `${Math.floor(Math.random() * 77) + 33}`;
|
|
||||||
tag.style.height = `${Math.floor(Math.random() * 15) + 23}`;
|
|
||||||
containerFill.appendChild(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
let redIndex = 0;
|
|
||||||
let redAmount = 5;
|
|
||||||
|
|
||||||
const redCount = Math.min(5, tags.length);
|
|
||||||
const redIndexes = [];
|
|
||||||
|
|
||||||
while (redIndexes.length < redCount) {
|
|
||||||
const randomIdx = Math.floor(Math.random() * tags.length);
|
|
||||||
if (!redIndexes.includes(randomIdx)) redIndexes.push(randomIdx);
|
|
||||||
}
|
|
||||||
console.log("redIndexes", redIndexes);
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 初始创建标签
|
|
||||||
|
|
||||||
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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -44,60 +44,6 @@
|
|||||||
background: linear-gradient(180deg, #1e135e 0%, #5241b0 100%);
|
background: linear-gradient(180deg, #1e135e 0%, #5241b0 100%);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.content .introduce .sidebar {
|
|
||||||
position: absolute;
|
|
||||||
top: 175px;
|
|
||||||
right: 100px;
|
|
||||||
z-index: 10;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .pointer {
|
|
||||||
margin-bottom: 192px;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .pointer .item {
|
|
||||||
width: 4px;
|
|
||||||
height: 50px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .pointer .item.active {
|
|
||||||
background-color: #f3974b;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .pointer .item.active::after {
|
|
||||||
content: attr(data-index);
|
|
||||||
position: absolute;
|
|
||||||
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
color: #f3974b;
|
|
||||||
font-size: 14px;
|
|
||||||
top: 0;
|
|
||||||
left: -21px;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .arrow {
|
|
||||||
width: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .arrow .item {
|
|
||||||
width: 12px;
|
|
||||||
height: 7px;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .arrow .item.orange {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .arrow .item.top.orange {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .arrow .item:not(:last-of-type) {
|
|
||||||
margin-bottom: 34px;
|
|
||||||
}
|
|
||||||
.content .introduce .sidebar .arrow .item.bottom.white {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
.content .introduce .head {
|
.content .introduce .head {
|
||||||
height: 116px;
|
height: 116px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.101961);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.101961);
|
||||||
@@ -109,12 +55,13 @@
|
|||||||
.content .introduce .box {
|
.content .introduce .box {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 685px;
|
height: 685px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.content .introduce .box .info {
|
.content .introduce .box .info {
|
||||||
padding-top: 60px;
|
padding-top: 60px;
|
||||||
}
|
display: flex;
|
||||||
.content .introduce .box .info .brand {
|
flex-direction: column;
|
||||||
margin-bottom: 175px;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.content .introduce .box .info .brand .fill {
|
.content .introduce .box .info .brand .fill {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@@ -132,6 +79,7 @@
|
|||||||
}
|
}
|
||||||
.content .introduce .box .info .award {
|
.content .introduce .box .info .award {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
margin-bottom: 177px;
|
||||||
}
|
}
|
||||||
.content .introduce .box .info .award .title {
|
.content .introduce .box .info .award .title {
|
||||||
width: 101px;
|
width: 101px;
|
||||||
@@ -221,6 +169,60 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.content .introduce .box .sidebar {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
right: -4px;
|
||||||
|
z-index: 10;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .pointer {
|
||||||
|
margin-bottom: 192px;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .pointer .item {
|
||||||
|
width: 4px;
|
||||||
|
height: 50px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .pointer .item.active {
|
||||||
|
background-color: #f3974b;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .pointer .item.active::after {
|
||||||
|
content: attr(data-index);
|
||||||
|
position: absolute;
|
||||||
|
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
color: #f3974b;
|
||||||
|
font-size: 14px;
|
||||||
|
top: 0;
|
||||||
|
left: -21px;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .arrow {
|
||||||
|
width: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .arrow .item {
|
||||||
|
width: 12px;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .arrow .item.orange {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .arrow .item.top.orange {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .arrow .item:not(:last-of-type) {
|
||||||
|
margin-bottom: 34px;
|
||||||
|
}
|
||||||
|
.content .introduce .box .sidebar .arrow .item.bottom.white {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
.content .introduce .trait {
|
.content .introduce .trait {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
height: 420px;
|
height: 420px;
|
||||||
@@ -326,6 +328,7 @@
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.content .works .mv-box .item {
|
.content .works .mv-box .item {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
@@ -859,7 +862,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #4c4c4c;
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
.content .preview .close {
|
.content .preview .close {
|
||||||
|
|||||||
@@ -48,68 +48,6 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
position: absolute;
|
|
||||||
top: 175px;
|
|
||||||
right: 100px;
|
|
||||||
z-index: 10;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
.pointer {
|
|
||||||
margin-bottom: 192px;
|
|
||||||
.item {
|
|
||||||
width: 4px;
|
|
||||||
height: 50px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
background-color: rgba(255, 255, 255, 1);
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: rgba(243, 151, 75, 1);
|
|
||||||
&::after {
|
|
||||||
content: attr(data-index);
|
|
||||||
position: absolute;
|
|
||||||
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
color: #f3974b;
|
|
||||||
font-size: 14px;
|
|
||||||
top: 0;
|
|
||||||
left: -21px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
width: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.item {
|
|
||||||
width: 12px;
|
|
||||||
height: 7px;
|
|
||||||
|
|
||||||
&.orange {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.top.orange {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:last-of-type) {
|
|
||||||
margin-bottom: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.bottom.white {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.head {
|
.head {
|
||||||
height: 116px;
|
height: 116px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.101961);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.101961);
|
||||||
@@ -123,10 +61,15 @@
|
|||||||
.box {
|
.box {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 685px;
|
height: 685px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
padding-top: 60px;
|
padding-top: 60px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
.brand {
|
.brand {
|
||||||
margin-bottom: 175px;
|
// margin-bottom: 175px;
|
||||||
|
|
||||||
.fill {
|
.fill {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@@ -147,6 +90,7 @@
|
|||||||
|
|
||||||
.award {
|
.award {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
margin-bottom: 177px;
|
||||||
.title {
|
.title {
|
||||||
width: 101px;
|
width: 101px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -250,6 +194,68 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
right: -4px;
|
||||||
|
z-index: 10;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
.pointer {
|
||||||
|
margin-bottom: 192px;
|
||||||
|
.item {
|
||||||
|
width: 4px;
|
||||||
|
height: 50px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: rgba(243, 151, 75, 1);
|
||||||
|
&::after {
|
||||||
|
content: attr(data-index);
|
||||||
|
position: absolute;
|
||||||
|
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
color: #f3974b;
|
||||||
|
font-size: 14px;
|
||||||
|
top: 0;
|
||||||
|
left: -21px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
width: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 12px;
|
||||||
|
height: 7px;
|
||||||
|
|
||||||
|
&.orange {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.top.orange {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
margin-bottom: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bottom.white {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.trait {
|
.trait {
|
||||||
@@ -372,6 +378,7 @@
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
@@ -998,7 +1005,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #4c4c4c;
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
|
|||||||
@@ -354,6 +354,7 @@
|
|||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
color: #583a05 !important;
|
color: #583a05 !important;
|
||||||
background-color: #f19a04 !important;
|
background-color: #f19a04 !important;
|
||||||
|
z-index: 100 !important;
|
||||||
}
|
}
|
||||||
.tag-item.item2 {
|
.tag-item.item2 {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@@ -386,3 +387,173 @@
|
|||||||
color: #62263c !important;
|
color: #62263c !important;
|
||||||
background: linear-gradient(180deg, #ff8eba 0%, #f4458c 100%);
|
background: linear-gradient(180deg, #ff8eba 0%, #f4458c 100%);
|
||||||
}
|
}
|
||||||
|
.bottom-play {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 76px;
|
||||||
|
background-color: #353535;
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: space-between;
|
||||||
|
animation: fadeInUp 0.3s ease forwards;
|
||||||
|
min-width: 1200px;
|
||||||
|
}
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
0% {
|
||||||
|
bottom: -76px;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-left {
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-left .img {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-left .name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
max-width: 240px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .operate {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .operate .cut {
|
||||||
|
width: 14px;
|
||||||
|
height: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .operate .speed {
|
||||||
|
width: 16px;
|
||||||
|
height: 12px;
|
||||||
|
margin: 0 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .operate .play {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .time-display {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 10px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .progress-bar {
|
||||||
|
height: 10px;
|
||||||
|
width: 500px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .progress-bar .bar {
|
||||||
|
height: 4px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .progress-bar .bar.white {
|
||||||
|
width: 0;
|
||||||
|
border-radius: 10px 0 0 10px;
|
||||||
|
background-color: #5241b0;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .progress-bar .bar.white::before {
|
||||||
|
content: "";
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #f3974b;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: -5px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-middle .progress-bar .bar.black {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border-radius: 0 10px 10px 0;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 18px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item .svg {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item .sound-control {
|
||||||
|
justify-content: center;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
height: 151px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
position: absolute;
|
||||||
|
top: -158px;
|
||||||
|
width: 42px;
|
||||||
|
align-items: center;
|
||||||
|
background: #1f1f1f;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item .sound-control::after {
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
height: 7px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -7px;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item .sound-control .value {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item .sound-control .progress {
|
||||||
|
width: 4px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item .sound-control .progress .bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.bottom-play .bottom-right .item .sound-control .progress .bar::before {
|
||||||
|
content: "";
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #f3974b;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
left: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|||||||
@@ -426,6 +426,7 @@
|
|||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
color: rgb(88, 58, 5) !important;
|
color: rgb(88, 58, 5) !important;
|
||||||
background-color: rgba(241, 154, 4, 1) !important;
|
background-color: rgba(241, 154, 4, 1) !important;
|
||||||
|
z-index: 100 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.item2 {
|
&.item2 {
|
||||||
@@ -463,3 +464,200 @@
|
|||||||
background: linear-gradient(180deg, #ff8eba 0%, #f4458c 100%);
|
background: linear-gradient(180deg, #ff8eba 0%, #f4458c 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-play {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 76px;
|
||||||
|
background-color: #353535;
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: space-between;
|
||||||
|
animation: fadeInUp 0.3s ease forwards;
|
||||||
|
min-width: 1200px;
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
0% {
|
||||||
|
bottom: -76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-left {
|
||||||
|
padding-left: 12px;
|
||||||
|
|
||||||
|
.img {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
max-width: 240px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-middle {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.operate {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.cut {
|
||||||
|
width: 14px;
|
||||||
|
height: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed {
|
||||||
|
width: 16px;
|
||||||
|
height: 12px;
|
||||||
|
margin: 0 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-display {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 10px;
|
||||||
|
width: 500px;
|
||||||
|
margin: 0 10px;
|
||||||
|
.bar {
|
||||||
|
height: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.white {
|
||||||
|
width: 0;
|
||||||
|
border-radius: 10px 0 0 10px;
|
||||||
|
background-color: #5241b0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #f3974b;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: -5px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.black {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border-radius: 0 10px 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-right: 12px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 18px;
|
||||||
|
.svg {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
// &:hover {
|
||||||
|
// .sound-control {
|
||||||
|
// display: flex;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
.sound-control {
|
||||||
|
justify-content: center;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
height: 151px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
position: absolute;
|
||||||
|
top: -158px;
|
||||||
|
width: 42px;
|
||||||
|
align-items: center;
|
||||||
|
background: #1f1f1f;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: auto;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
height: 7px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
width: 4px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #f3974b;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
left: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
BIN
static/img/favicon.ico
Normal file
BIN
static/img/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
343
static/js/song-request-station.js
Normal file
343
static/js/song-request-station.js
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
const { createApp, ref, onMounted, nextTick, onUnmounted, computed } = Vue;
|
||||||
|
const search = createApp({
|
||||||
|
setup() {
|
||||||
|
// 标签数据
|
||||||
|
let tags = ref([]);
|
||||||
|
let tagsFill = ref([]);
|
||||||
|
|
||||||
|
// 获取容器
|
||||||
|
const containerFill = ref(null);
|
||||||
|
const container = ref(null);
|
||||||
|
|
||||||
|
// 计算容器尺寸
|
||||||
|
const getContainerDimensions = () => {
|
||||||
|
const containerWidth = container.value.offsetWidth;
|
||||||
|
const containerHeight = container.value.offsetHeight;
|
||||||
|
return { containerWidth, containerHeight };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始创建标签
|
||||||
|
const init = () => {
|
||||||
|
fetch("./static/js/music.json")
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) throw new Error(`请求失败:${response.status}`);
|
||||||
|
|
||||||
|
// 将响应解析为 JSON 格式
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
// 成功获取 JSON 数据,后续处理(如使用 tags 数组)
|
||||||
|
console.log("读取到的 JSON 数据:", data);
|
||||||
|
// data.data.length = 1
|
||||||
|
let tagsData = data.data || []; // 提取 tags 数组
|
||||||
|
console.log("tagsData", tagsData);
|
||||||
|
|
||||||
|
const { containerWidth, containerHeight } = getContainerDimensions();
|
||||||
|
|
||||||
|
const redCount = Math.min(5, tagsData.length);
|
||||||
|
const redIndexes = [];
|
||||||
|
while (redIndexes.length < redCount) {
|
||||||
|
const randomIdx = Math.floor(Math.random() * tagsData.length);
|
||||||
|
if (!redIndexes.includes(randomIdx)) redIndexes.push(randomIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsData.forEach((item, index) => {
|
||||||
|
item["type"] = getRandomOutcome();
|
||||||
|
if (redIndexes.includes(index)) item["isred"] = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
tags.value = tagsData;
|
||||||
|
|
||||||
|
const fillLength = tags.value.length * 1.5;
|
||||||
|
let tagAll = [];
|
||||||
|
for (let i = 0; i < fillLength; i++) {
|
||||||
|
tagAll.push({
|
||||||
|
type: Math.floor(Math.random() * 5) + 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsFill.value = tagAll;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
tagCloud({
|
||||||
|
selector: "#bubbleContainerFill", // 元素选择器,id 或 class
|
||||||
|
radius: [containerWidth / 2, containerHeight / 2], // 滚动横/纵轴半径, 默认60,单位px,取值60,[60],[60, 60]
|
||||||
|
mspeed: "normal", // 滚动最大速度, 取值: 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
|
||||||
|
});
|
||||||
|
|
||||||
|
tagCloud({
|
||||||
|
selector: "#bubbleContainer", // 元素选择器,id 或 class
|
||||||
|
radius: [containerWidth / 2, containerHeight / 2],
|
||||||
|
mspeed: "normal",
|
||||||
|
ispeed: "normal",
|
||||||
|
direction: 135,
|
||||||
|
keep: false,
|
||||||
|
multicolour: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成单次随机结果的函数
|
||||||
|
const 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init();
|
||||||
|
|
||||||
|
// 添加进度更新事件监听器
|
||||||
|
if (audioPlayer.value) {
|
||||||
|
volume.value = audioPlayer.value.volume * 100;
|
||||||
|
audioPlayer.value.addEventListener("timeupdate", getProgress);
|
||||||
|
audioPlayer.value.addEventListener("loadedmetadata", getProgress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清理事件监听器
|
||||||
|
onUnmounted(() => {
|
||||||
|
audioPlayer?.value?.removeEventListener("timeupdate", getProgress);
|
||||||
|
audioPlayer?.value?.removeEventListener("loadedmetadata", getProgress);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMouseleave = (e) => {
|
||||||
|
e.target.style.animationPlayState = "running";
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseenter = (e) => {
|
||||||
|
e.target.style.animationPlayState = "paused";
|
||||||
|
};
|
||||||
|
|
||||||
|
const audioPlayer = ref(null);
|
||||||
|
|
||||||
|
const clickSongs = (songs) => {
|
||||||
|
console.log("songs", songs.length);
|
||||||
|
const randomIndex = Math.floor(Math.random() * songs.length);
|
||||||
|
const item = songs[randomIndex];
|
||||||
|
console.log("随机选择的 item:", item);
|
||||||
|
manageAudio(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 管理音频播放
|
||||||
|
const manageAudio = (item) => {
|
||||||
|
const audio = audioPlayer.value;
|
||||||
|
closeAll();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (audio?.src != item.path) audio.src = item.path;
|
||||||
|
|
||||||
|
// audio.src = "https://app.gter.net/image/miniApp/mp3/1.mp3";
|
||||||
|
audio.play().then(() => {
|
||||||
|
playData.value = { ...item, state: true };
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重新播放
|
||||||
|
const rePlay = () => {
|
||||||
|
if (!playData.value) return;
|
||||||
|
const item = playData.value;
|
||||||
|
manageAudio(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 播放 组件数据
|
||||||
|
let playData = ref(null);
|
||||||
|
|
||||||
|
// 响应式数据:音量值、是否静音
|
||||||
|
let volume = ref(100);
|
||||||
|
|
||||||
|
// 计算并设置音量百分比
|
||||||
|
const setVolumePercentage = (percentage) => {
|
||||||
|
const volumePercent = Math.max(0, Math.min(100, percentage));
|
||||||
|
volume.value = Math.abs(~~volumePercent);
|
||||||
|
|
||||||
|
// 设置音频元素的音量(范围是0-1)
|
||||||
|
if (audioPlayer.value) audioPlayer.value.volume = volume.value / 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理音量进度条点击
|
||||||
|
const handleVolumeClick = (event) => {
|
||||||
|
// 获取进度条元素
|
||||||
|
const progressBar = event.currentTarget;
|
||||||
|
const rect = progressBar.getBoundingClientRect();
|
||||||
|
|
||||||
|
const clickPosition = rect.bottom - event.clientY;
|
||||||
|
const percentage = (clickPosition / rect.height) * 100;
|
||||||
|
|
||||||
|
setVolumePercentage(percentage);
|
||||||
|
};
|
||||||
|
|
||||||
|
let volumeShow = ref(false);
|
||||||
|
// 处理音量进度条拖拽
|
||||||
|
let isDragging = false;
|
||||||
|
|
||||||
|
const startDrag = (event) => {
|
||||||
|
isDragging = true;
|
||||||
|
handleVolumeDrag(event);
|
||||||
|
// 添加事件监听器
|
||||||
|
document.addEventListener("mousemove", handleVolumeDrag);
|
||||||
|
document.addEventListener("mouseup", stopDrag);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVolumeDrag = (event) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
// 获取音量进度条元素
|
||||||
|
const progressBar = document.querySelector(".sound-control .progress");
|
||||||
|
if (!progressBar) return;
|
||||||
|
|
||||||
|
const rect = progressBar.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 计算拖拽位置相对于进度条的比例
|
||||||
|
let dragPosition = rect.bottom - event.clientY;
|
||||||
|
// 限制在进度条范围内
|
||||||
|
dragPosition = Math.max(0, Math.min(dragPosition, rect.height));
|
||||||
|
|
||||||
|
const percentage = (dragPosition / rect.height) * 100;
|
||||||
|
setVolumePercentage(percentage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopDrag = () => {
|
||||||
|
isDragging = false;
|
||||||
|
document.removeEventListener("mousemove", handleVolumeDrag);
|
||||||
|
document.removeEventListener("mouseup", stopDrag);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVolumeShow = () => (volumeShow.value = true);
|
||||||
|
|
||||||
|
const handleVolumeHide = () => (volumeShow.value = false);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 确保移除所有拖拽相关事件
|
||||||
|
document.removeEventListener("mousemove", handleVolumeDrag);
|
||||||
|
document.removeEventListener("mouseup", stopDrag);
|
||||||
|
document.removeEventListener("mousemove", handleBarDragBottomDrag);
|
||||||
|
document.removeEventListener("mouseup", stopBarDragBottom);
|
||||||
|
});
|
||||||
|
|
||||||
|
const progress = ref(0); // 播放进度百分比
|
||||||
|
// 响应式变量存储当前播放时间和总时长
|
||||||
|
const currentTimeFormatted = ref("00:00");
|
||||||
|
const durationFormatted = ref("00:00");
|
||||||
|
|
||||||
|
// 格式化时间函数:将秒数转换为 MM:SS 格式
|
||||||
|
const formatTime = (seconds) => {
|
||||||
|
const mins = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新进度的函数
|
||||||
|
const getProgress = () => {
|
||||||
|
if (!audioPlayer.value) return;
|
||||||
|
const currentTime = audioPlayer.value.currentTime || 0;
|
||||||
|
const duration = audioPlayer.value.duration || 0;
|
||||||
|
const p = duration > 0 ? (currentTime / duration) * 100 : 0;
|
||||||
|
|
||||||
|
progress.value = p;
|
||||||
|
// 更新时间显示
|
||||||
|
currentTimeFormatted.value = formatTime(currentTime);
|
||||||
|
durationFormatted.value = formatTime(duration);
|
||||||
|
|
||||||
|
console.log("p", p);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 快进 和 后退 10秒
|
||||||
|
const fastForward = (type = "fast") => {
|
||||||
|
if (!audioPlayer.value) return;
|
||||||
|
const src = playData.value?.playurl || "";
|
||||||
|
const area = playData.value?.area || "";
|
||||||
|
|
||||||
|
if (audioPlayer.value.src != src) {
|
||||||
|
manageAudio(src, area);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTime = audioPlayer.value.currentTime || 0;
|
||||||
|
const duration = audioPlayer.value.duration || 0;
|
||||||
|
let newTime = 0;
|
||||||
|
if (type == "fast") newTime = Math.min(currentTime + 10, duration);
|
||||||
|
else newTime = Math.max(currentTime - 10, 0);
|
||||||
|
audioPlayer.value.currentTime = newTime;
|
||||||
|
getProgress();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭所有播放
|
||||||
|
const closeAll = () => {
|
||||||
|
audioPlayer.value.pause();
|
||||||
|
|
||||||
|
playData.value && (playData.value.state = false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理音量进度条拖拽
|
||||||
|
let isBarBottomDragging = false;
|
||||||
|
|
||||||
|
const startBarDragBottom = (event) => {
|
||||||
|
isBarBottomDragging = true;
|
||||||
|
handleBarDragBottomDrag(event);
|
||||||
|
// 添加事件监听器
|
||||||
|
document.addEventListener("mousemove", handleBarDragBottomDrag);
|
||||||
|
document.addEventListener("mouseup", stopBarDragBottom);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopBarDragBottom = () => {
|
||||||
|
isBarBottomDragging = false;
|
||||||
|
document.removeEventListener("mousemove", handleBarDragBottomDrag);
|
||||||
|
document.removeEventListener("mouseup", stopBarDragBottom);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 直接点击进度条 跳转
|
||||||
|
const handleBarDragBottomClick = (event) => {
|
||||||
|
// 获取进度条元素
|
||||||
|
const progressBar = event.currentTarget;
|
||||||
|
const rect = progressBar.getBoundingClientRect();
|
||||||
|
const clickPosition = event.clientX - rect.left;
|
||||||
|
const percentage = clickPosition / rect.width;
|
||||||
|
|
||||||
|
// 限制百分比在0-100之间
|
||||||
|
const clampedPercentage = Math.max(0, Math.min(100, percentage));
|
||||||
|
updatePlay(clampedPercentage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBarDragBottomDrag = (event) => {
|
||||||
|
if (!isBarBottomDragging) return;
|
||||||
|
|
||||||
|
// 获取音量进度条元素
|
||||||
|
const progressBar = document.querySelector(".bottom-play .middle .progress-bar");
|
||||||
|
if (!progressBar) return;
|
||||||
|
|
||||||
|
const rect = progressBar.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 计算拖拽位置相对于进度条的比例
|
||||||
|
let dragPosition = event.clientX - rect.left;
|
||||||
|
// 限制在进度条范围内
|
||||||
|
dragPosition = Math.max(0, Math.min(dragPosition, rect.width));
|
||||||
|
|
||||||
|
const percentage = dragPosition / rect.width;
|
||||||
|
updatePlay(percentage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePlay = (percentage) => {
|
||||||
|
if (!audioPlayer.value) return;
|
||||||
|
const duration = audioPlayer.value.duration || 0;
|
||||||
|
let newTime = duration * percentage;
|
||||||
|
newTime = Math.max(0, Math.min(duration, newTime));
|
||||||
|
audioPlayer.value.currentTime = newTime;
|
||||||
|
getProgress();
|
||||||
|
};
|
||||||
|
|
||||||
|
return { handleBarDragBottomClick, closeAll, fastForward, progress, durationFormatted, currentTimeFormatted, rePlay, handleVolumeHide, handleVolumeShow, stopDrag, handleVolumeDrag, startDrag, volumeShow, handleVolumeClick, setVolumePercentage, volume, audioPlayer, playData, handleMouseleave, handleMouseenter, clickSongs, tags, tagsFill, containerFill, container };
|
||||||
|
},
|
||||||
|
}).mount("#song-request-station");
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
/*
|
|
||||||
* 3d标签云
|
|
||||||
* 功能:鼠标移入标签,当前标签静止放大
|
|
||||||
* 说明:radius 控制滚动区域(横椭圆、纵椭圆、正圆)
|
|
||||||
* 版本:2.2
|
|
||||||
* */
|
|
||||||
|
|
||||||
window.tagCloud = (function (win, doc) {
|
|
||||||
// 判断对象
|
|
||||||
function isObject(obj) {
|
|
||||||
return Object.prototype.toString.call(obj) === '[object Object]'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构造函数
|
|
||||||
function TagCloud(options) {
|
|
||||||
var self = this;
|
|
||||||
self.config = TagCloud._getConfig(options);
|
|
||||||
self.box = self.config.element; // 组件元素
|
|
||||||
self.fontsize = self.config.fontsize; // 平均字体大小
|
|
||||||
|
|
||||||
if (Number.isInteger(self.config.radius)) {
|
|
||||||
self._radiusX = self._radiusY = self.config.radius
|
|
||||||
} else if (self.config.radius instanceof Array) {
|
|
||||||
if (self.config.radius.length === 1) {
|
|
||||||
self._radiusX = self._radiusY = self.config.radius[0]
|
|
||||||
} else {
|
|
||||||
self._radiusX = self.config.radius[0]
|
|
||||||
self._radiusY = self.config.radius[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.radius = self._radiusX; // 滚动半径
|
|
||||||
|
|
||||||
_ratio = Math.round(self._radiusX * 10 / self._radiusY) / 10; // 滚动区域比例,保留一位小数
|
|
||||||
if(_ratio < 1 ) { // 焦点在Y轴的椭圆
|
|
||||||
self.ratioX = _ratio;
|
|
||||||
self.ratioY = 1;
|
|
||||||
self.radius = self._radiusY; // 滚动半径,选择长半径
|
|
||||||
} else if(_ratio > 1 ) { // 焦点在X轴的椭圆
|
|
||||||
self.ratioX = 1;
|
|
||||||
self.ratioY = _ratio;
|
|
||||||
} else {
|
|
||||||
self.ratioX = self.ratioY = 1; // 正圆
|
|
||||||
}
|
|
||||||
|
|
||||||
self.depth = 2 * self.radius; // 滚动深度
|
|
||||||
self.size = 2 * self.radius; // 随鼠标滚动变速作用区域
|
|
||||||
self.mspeed = TagCloud._getMsSpeed(self.config.mspeed);
|
|
||||||
self.ispeed = TagCloud._getIsSpeed(self.config.ispeed);
|
|
||||||
self.items = self._getItems();
|
|
||||||
|
|
||||||
self.direction = self.config.direction; // 初始滚动方向
|
|
||||||
self.keep = self.config.keep; // 鼠标移出后是否保持之前滚动
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
self.active = false; // 是否为激活状态
|
|
||||||
self.lasta = 1;
|
|
||||||
self.lastb = 1;
|
|
||||||
self.mouseX0 = self.ispeed * Math.sin(self.direction * Math.PI / 180); // 鼠标与滚动圆心x轴初始距离
|
|
||||||
self.mouseY0 = -self.ispeed * Math.cos(self.direction * Math.PI / 180); // 鼠标与滚动圆心y轴初始距离
|
|
||||||
self.mouseX = self.mouseX0; // 鼠标与滚动圆心x轴距离
|
|
||||||
self.mouseY = self.mouseY0; // 鼠标与滚动圆心y轴距离
|
|
||||||
self.index = -1;
|
|
||||||
|
|
||||||
// 鼠标移入
|
|
||||||
TagCloud._on(self.box, 'mouseover', function () {
|
|
||||||
self.active = true;
|
|
||||||
});
|
|
||||||
// 鼠标移出
|
|
||||||
TagCloud._on(self.box, 'mouseout', function () {
|
|
||||||
self.active = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 鼠标在内移动
|
|
||||||
TagCloud._on(self.keep ? win : self.box, 'mousemove', function (ev) {
|
|
||||||
var oEvent = win.event || ev;
|
|
||||||
var boxPosition = self.box.getBoundingClientRect();
|
|
||||||
self.mouseX = (oEvent.clientX - (boxPosition.left + self.box.offsetWidth / 2)) / 5;
|
|
||||||
self.mouseY = (oEvent.clientY - (boxPosition.top + self.box.offsetHeight / 2)) / 5;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (var j = 0, len = self.items.length; j < len; j++) {
|
|
||||||
self.items[j].element.index = j;
|
|
||||||
// 鼠标移出子元素,当前元素静止放大
|
|
||||||
self.items[j].element.onmouseover = function () {
|
|
||||||
self.index = this.index;
|
|
||||||
}
|
|
||||||
// 鼠标移出子元素,当前元素继续滚动
|
|
||||||
self.items[j].element.onmouseout = function () {
|
|
||||||
self.index = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定时更新
|
|
||||||
TagCloud.boxs.push(self.box);
|
|
||||||
self.update(self); // 初始更新
|
|
||||||
self.box.style.visibility = "visible";
|
|
||||||
self.box.style.position = 'relative';
|
|
||||||
for (var j = 0, len = self.items.length; j < len; j++) {
|
|
||||||
self.items[j].element.style.position = "absolute";
|
|
||||||
self.items[j].element.style.zIndex = j + 1;
|
|
||||||
}
|
|
||||||
self.up = setInterval(function () {
|
|
||||||
self.update(self)
|
|
||||||
}, 30)
|
|
||||||
}
|
|
||||||
|
|
||||||
//实例
|
|
||||||
TagCloud.boxs = []; //实例元素数组
|
|
||||||
// 静态方法们
|
|
||||||
TagCloud._set = function (element) {
|
|
||||||
if (TagCloud.boxs.indexOf(element) === -1) { // ie8不支持数组的indexOf方法,所以自定义indexOf
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//添加数组IndexOf方法
|
|
||||||
if (!Array.prototype.indexOf) {
|
|
||||||
// Array.prototype.indexOf = function (elt /*, from*/) {
|
|
||||||
Array.prototype.indexOf = function (elt) {
|
|
||||||
var len = this.length >>> 0;
|
|
||||||
var from = Number(arguments[1]) || 0;
|
|
||||||
from = (from < 0)
|
|
||||||
? Math.ceil(from)
|
|
||||||
: Math.floor(from);
|
|
||||||
if (from < 0)
|
|
||||||
from += len;
|
|
||||||
|
|
||||||
for (; from < len; from++) {
|
|
||||||
if (from in this && this[from] === elt)
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
TagCloud._getConfig = function (config) {
|
|
||||||
var defaultConfig = { // 默认值
|
|
||||||
fontsize: 16, // 基本字体大小, 单位px
|
|
||||||
radius: 60, // 滚动纵轴半径, 默认60, 单位px,取值60,[60],[60, 60]
|
|
||||||
mspeed: "normal", // 滚动最大速度, 取值: slow, normal(默认), fast
|
|
||||||
ispeed: "normal", // 滚动初速度, 取值: slow, normal(默认), fast
|
|
||||||
direction: 135, // 初始滚动方向, 取值角度(顺时针360): 0对应top, 90对应left, 135对应right-bottom(默认)...
|
|
||||||
keep: true, // 鼠标移出组件后是否继续随鼠标滚动, 取值: false, true(默认) 对应 减速至初速度滚动, 随鼠标滚动
|
|
||||||
multicolour: true // 是否为彩色字体,颜色随机,取值:true(默认),false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isObject(config)) {
|
|
||||||
for (var i in config) {
|
|
||||||
if (config.hasOwnProperty(i)) {//hasOwnProperty()用来判断一个属性是定义在对象本身而不是继承自原型链
|
|
||||||
defaultConfig[i] = config[i]; //用户配置
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultConfig;// 配置 Merge
|
|
||||||
};
|
|
||||||
|
|
||||||
TagCloud._getMsSpeed = function (mspeed) { //滚动最大速度
|
|
||||||
var speedMap = {
|
|
||||||
slow: 1.5,
|
|
||||||
normal: 3,
|
|
||||||
fast: 5
|
|
||||||
};
|
|
||||||
return speedMap[mspeed] || 3;
|
|
||||||
};
|
|
||||||
TagCloud._getIsSpeed = function (ispeed) { //滚动初速度
|
|
||||||
var speedMap = {
|
|
||||||
slow: 10,
|
|
||||||
normal: 25,
|
|
||||||
fast: 50
|
|
||||||
};
|
|
||||||
return speedMap[ispeed] || 25;
|
|
||||||
};
|
|
||||||
TagCloud._getSc = function (a, b) {
|
|
||||||
var l = Math.PI / 180;
|
|
||||||
//数组顺序0,1,2,3表示asin,acos,bsin,bcos
|
|
||||||
return [
|
|
||||||
Math.sin(a * l),
|
|
||||||
Math.cos(a * l),
|
|
||||||
Math.sin(b * l),
|
|
||||||
Math.cos(b * l)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
TagCloud._on = function (ele, eve, handler, cap) {
|
|
||||||
if (ele.addEventListener) {
|
|
||||||
ele.addEventListener(eve, handler, cap);
|
|
||||||
} else if (ele.attachEvent) {
|
|
||||||
ele.attachEvent('on' + eve, handler);
|
|
||||||
} else {
|
|
||||||
ele['on' + eve] = handler;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 原型方法
|
|
||||||
TagCloud.prototype = {
|
|
||||||
constructor: TagCloud, // 反向引用构造器
|
|
||||||
|
|
||||||
update: function () {
|
|
||||||
var self = this, a, b;
|
|
||||||
|
|
||||||
if (!self.active && !self.keep) {
|
|
||||||
self.mouseX = Math.abs(self.mouseX - self.mouseX0) < 1 ? self.mouseX0 : (self.mouseX + self.mouseX0) / 2; //重置鼠标与滚动圆心x轴距离
|
|
||||||
self.mouseY = Math.abs(self.mouseY - self.mouseY0) < 1 ? self.mouseY0 : (self.mouseY + self.mouseY0) / 2; //重置鼠标与滚动圆心y轴距离
|
|
||||||
}
|
|
||||||
|
|
||||||
a = -(Math.min(Math.max(-self.mouseY, -self.size), self.size) * 2 / self.radius) * self.mspeed;
|
|
||||||
b = (Math.min(Math.max(-self.mouseX, -self.size), self.size) * 2 / self.radius) * self.mspeed;
|
|
||||||
|
|
||||||
if (Math.abs(a) <= 0.01 && Math.abs(b) <= 0.01) { return; }
|
|
||||||
|
|
||||||
self.lasta = a;
|
|
||||||
self.lastb = b;
|
|
||||||
|
|
||||||
var sc = TagCloud._getSc(a, b);
|
|
||||||
|
|
||||||
for (var j = 0, len = self.items.length; j < len; j++) {
|
|
||||||
|
|
||||||
var rx1 = self.items[j].x,
|
|
||||||
ry1 = self.items[j].y * sc[1] + self.items[j].z * (-sc[0]),
|
|
||||||
rz1 = self.items[j].y * sc[0] + self.items[j].z * sc[1];
|
|
||||||
|
|
||||||
var rx2 = rx1 * sc[3] + rz1 * sc[2],
|
|
||||||
ry2 = ry1,
|
|
||||||
rz2 = rz1 * sc[3] - rx1 * sc[2];
|
|
||||||
|
|
||||||
if (self.index == j) {
|
|
||||||
self.items[j].scale = 1; //取值范围0.6 ~ 3
|
|
||||||
self.items[j].fontsize = 18;
|
|
||||||
self.items[j].alpha = 1;
|
|
||||||
self.items[j].element.style.zIndex = 99;
|
|
||||||
} else {
|
|
||||||
var per = self.depth / (self.depth + rz2);
|
|
||||||
self.items[j].x = rx2;
|
|
||||||
self.items[j].y = ry2;
|
|
||||||
self.items[j].z = rz2;
|
|
||||||
|
|
||||||
self.items[j].scale = per; //取值范围0.6 ~ 3
|
|
||||||
self.items[j].fontsize = Math.ceil(per * 2) + self.fontsize - 6;
|
|
||||||
self.items[j].alpha = 1.5 * per - 0.5;
|
|
||||||
self.items[j].element.style.zIndex = Math.ceil(per * 10 - 5);
|
|
||||||
}
|
|
||||||
self.items[j].element.style.fontSize = self.items[j].fontsize + "px";
|
|
||||||
self.items[j].element.style.left = self.items[j].x * self.ratioX + (self.box.offsetWidth - self.items[j].offsetWidth) / 2 + "px";
|
|
||||||
self.items[j].element.style.top = self.items[j].y / self.ratioY + (self.box.offsetHeight - self.items[j].offsetHeight) / 2 + "px";
|
|
||||||
self.items[j].element.style.filter = "alpha(opacity=" + 100 * self.items[j].alpha + ")";
|
|
||||||
self.items[j].element.style.opacity = self.items[j].alpha;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getItems: function () {
|
|
||||||
var self = this,
|
|
||||||
items = [],
|
|
||||||
element = self.box.children, // children 全部是Element
|
|
||||||
length = element.length,
|
|
||||||
item;
|
|
||||||
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
item = {};
|
|
||||||
item.angle = {};
|
|
||||||
item.angle.phi = Math.acos(-1 + (2 * i + 1) / length);
|
|
||||||
item.angle.theta = Math.sqrt((length + 1) * Math.PI) * item.angle.phi;
|
|
||||||
item.element = element[i];
|
|
||||||
item.offsetWidth = item.element.offsetWidth;
|
|
||||||
item.offsetHeight = item.element.offsetHeight;
|
|
||||||
item.x = self.radius / 2 * 1.5 * Math.cos(item.angle.theta) * Math.sin(item.angle.phi);
|
|
||||||
item.y = self.radius / 2 * 1.5 * Math.sin(item.angle.theta) * Math.sin(item.angle.phi);
|
|
||||||
item.z = self.radius / 2 * 1.5 * Math.cos(item.angle.phi);
|
|
||||||
item.element.style.left = item.x * self.ratioX + ( self.box.offsetWidth - item.offsetWidth ) / 2 + "px";
|
|
||||||
item.element.style.top = item.y / self.ratioY + ( self.box.offsetHeight - item.offsetHeight ) / 2 + "px";
|
|
||||||
if (self.config.multicolour) { // 初始化文字颜色为彩色
|
|
||||||
_color = self._randomNumBoth(0, 360); // 定义色相 (0 到 360) - 0 (或 360) 红,120绿,180青,240蓝,300紫
|
|
||||||
_light = self._randomNumBoth(30, 60); // 定义亮度 0% 为暗, 50% 为普通, 100% 为白
|
|
||||||
item.element.style.color = "hsl(" + _color + ", 100%, " + _light + "%)"; // 中间值为饱和度; 0%灰色,100%全色
|
|
||||||
// item.element.style.color = 'rgb(' + parseInt(Math.random() * 255) + ',' + parseInt(Math.random() * 255) + ',' + parseInt(Math.random() * 255) + ')';
|
|
||||||
}
|
|
||||||
items.push(item);
|
|
||||||
}
|
|
||||||
return items; //单元素数组
|
|
||||||
},
|
|
||||||
|
|
||||||
// 取随机值,Min ≤ num ≤ Max
|
|
||||||
_randomNumBoth: function(Min, Max){
|
|
||||||
var Range = Max - Min;
|
|
||||||
var Rand = Math.random();
|
|
||||||
var num = Min + Math.round(Rand * Range); //四舍五入
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!doc.querySelectorAll) { // ie7不支持querySelectorAll,所以要重新定义
|
|
||||||
doc.querySelectorAll = function (selectors) {
|
|
||||||
var style = doc.createElement('style'), elements = [], element;
|
|
||||||
doc.documentElement.firstChild.appendChild(style);
|
|
||||||
doc._qsa = [];
|
|
||||||
|
|
||||||
style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
|
|
||||||
window.scrollBy(0, 0);
|
|
||||||
style.parentNode.removeChild(style);
|
|
||||||
|
|
||||||
while (doc._qsa.length) {
|
|
||||||
element = doc._qsa.shift();
|
|
||||||
element.style.removeAttribute('x-qsa');
|
|
||||||
elements.push(element);
|
|
||||||
}
|
|
||||||
doc._qsa = null;
|
|
||||||
return elements;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (options) { // factory
|
|
||||||
options = options || {}; // 短路语法
|
|
||||||
var selector = options.selector || '.tagcloud', // 默认选择class为tagcloud的元素
|
|
||||||
elements = doc.querySelectorAll(selector),
|
|
||||||
instance = [];
|
|
||||||
for (var index = 0, len = elements.length; index < len; index++) {
|
|
||||||
options.element = elements[index];
|
|
||||||
if (!!TagCloud._set(options.element)) {
|
|
||||||
instance.push(new TagCloud(options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
})(window, document)
|
|
||||||
@@ -104,7 +104,7 @@ window.tagCloud = (function (win, doc) {
|
|||||||
}
|
}
|
||||||
self.up = setInterval(function () {
|
self.up = setInterval(function () {
|
||||||
self.update(self);
|
self.update(self);
|
||||||
}, 5);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//实例
|
//实例
|
||||||
@@ -170,7 +170,6 @@ window.tagCloud = (function (win, doc) {
|
|||||||
//滚动初速度
|
//滚动初速度
|
||||||
var speedMap = {
|
var speedMap = {
|
||||||
slow: 10,
|
slow: 10,
|
||||||
slow2: 14,
|
|
||||||
normal: 25,
|
normal: 25,
|
||||||
fast: 50,
|
fast: 50,
|
||||||
};
|
};
|
||||||
|
|||||||
380
static/js/tagcloud-3.1.js
Normal file
380
static/js/tagcloud-3.1.js
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* 3D标签云(无缩放版)
|
||||||
|
* 功能:鼠标移入标签静止、自动3D旋转、GPU加速,移除所有放大缩小效果
|
||||||
|
* 说明:radius控制滚动区域,基于translate3d实现3D定位,全程保持标签原始尺寸
|
||||||
|
* 版本:3.1
|
||||||
|
* */
|
||||||
|
window.tagCloud = (function (win, doc) {
|
||||||
|
function isObject(obj) {
|
||||||
|
return Object.prototype.toString.call(obj) === "[object Object]";
|
||||||
|
}
|
||||||
|
|
||||||
|
function TagCloud(options) {
|
||||||
|
var self = this;
|
||||||
|
self.config = TagCloud._getConfig(options);
|
||||||
|
self.box = self.config.element;
|
||||||
|
self.fontsize = self.config.fontsize;
|
||||||
|
self.animDuration = self.config.animDuration;
|
||||||
|
self.perspective = self.config.perspective;
|
||||||
|
|
||||||
|
// 初始化滚动区域(半径与比例)
|
||||||
|
if (Number.isInteger(self.config.radius)) {
|
||||||
|
self._radiusX = self._radiusY = self.config.radius;
|
||||||
|
} else if (self.config.radius instanceof Array) {
|
||||||
|
self._radiusX = self.config.radius.length >= 1 ? self.config.radius[0] : 60;
|
||||||
|
self._radiusY = self.config.radius.length >= 2 ? self.config.radius[1] : self._radiusX;
|
||||||
|
}
|
||||||
|
self.radius = Math.max(self._radiusX, self._radiusY);
|
||||||
|
const ratio = Math.round((self._radiusX * 10) / self._radiusY) / 10;
|
||||||
|
self.ratioX = ratio < 1 ? ratio : 1;
|
||||||
|
self.ratioY = ratio > 1 ? ratio : 1;
|
||||||
|
|
||||||
|
// 基础参数
|
||||||
|
self.depth = 2 * self.radius;
|
||||||
|
self.mspeed = TagCloud._getMsSpeed(self.config.mspeed);
|
||||||
|
self.ispeed = TagCloud._getIsSpeed(self.config.ispeed);
|
||||||
|
self.items = self._getItems();
|
||||||
|
self.direction = self.config.direction;
|
||||||
|
self.keep = self.config.keep;
|
||||||
|
|
||||||
|
// 状态变量
|
||||||
|
self.active = false;
|
||||||
|
self.mouseX = 0;
|
||||||
|
self.mouseY = 0;
|
||||||
|
self.index = -1;
|
||||||
|
self.keyframeNames = [];
|
||||||
|
|
||||||
|
// 初始化容器样式(3D透视核心)
|
||||||
|
self.box.style.position = "relative";
|
||||||
|
self.box.style.visibility = "visible";
|
||||||
|
self.box.style.overflow = "hidden";
|
||||||
|
self.box.style.perspective = `${self.perspective}px`;
|
||||||
|
self.box.style.transformStyle = "preserve-3d";
|
||||||
|
|
||||||
|
// 事件绑定
|
||||||
|
self._bindEvents();
|
||||||
|
|
||||||
|
// 生成关键帧与动画
|
||||||
|
self._generateAllKeyframes();
|
||||||
|
self._applyAnimations();
|
||||||
|
|
||||||
|
// 实时更新
|
||||||
|
self._update = self._update.bind(self);
|
||||||
|
requestAnimationFrame(self._update);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态属性与方法
|
||||||
|
TagCloud.boxs = [];
|
||||||
|
|
||||||
|
// 数组indexOf兼容
|
||||||
|
if (!Array.prototype.indexOf) {
|
||||||
|
Array.prototype.indexOf = function (elt) {
|
||||||
|
var len = this.length >>> 0;
|
||||||
|
var from = Number(arguments[1]) || 0;
|
||||||
|
from = from < 0 ? Math.ceil(from) : Math.floor(from);
|
||||||
|
if (from < 0) from += len;
|
||||||
|
for (; from < len; from++) {
|
||||||
|
if (from in this && this[from] === elt) return from;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// querySelectorAll兼容
|
||||||
|
if (!doc.querySelectorAll) {
|
||||||
|
doc.querySelectorAll = function (selectors) {
|
||||||
|
var style = doc.createElement("style"),
|
||||||
|
elements = [],
|
||||||
|
element;
|
||||||
|
doc.documentElement.firstChild.appendChild(style);
|
||||||
|
doc._qsa = [];
|
||||||
|
style.styleSheet.cssText = selectors + "{x-qsa:expression(document._qsa && document._qsa.push(this))}";
|
||||||
|
window.scrollBy(0, 0);
|
||||||
|
style.parentNode.removeChild(style);
|
||||||
|
while (doc._qsa.length) {
|
||||||
|
element = doc._qsa.shift();
|
||||||
|
element.style.removeAttribute("x-qsa");
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
doc._qsa = null;
|
||||||
|
return elements;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置合并(移除hoverScale配置)
|
||||||
|
TagCloud._getConfig = function (config) {
|
||||||
|
const defaultConfig = {
|
||||||
|
selector: ".tagcloud",
|
||||||
|
fontsize: 16,
|
||||||
|
radius: [80, 60],
|
||||||
|
mspeed: "normal",
|
||||||
|
ispeed: "normal",
|
||||||
|
direction: 135,
|
||||||
|
keep: true,
|
||||||
|
multicolour: true,
|
||||||
|
animDuration: 15,
|
||||||
|
perspective: 800
|
||||||
|
};
|
||||||
|
if (isObject(config)) {
|
||||||
|
for (let i in config) {
|
||||||
|
if (config.hasOwnProperty(i) && i !== "hoverScale") { // 禁止传入缩放相关配置
|
||||||
|
defaultConfig[i] = config[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 滚动最大速度映射
|
||||||
|
TagCloud._getMsSpeed = function (mspeed) {
|
||||||
|
const speedMap = { slow: 1.2, normal: 5, fast: 4 };
|
||||||
|
return speedMap[mspeed] || 2.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始速度映射
|
||||||
|
TagCloud._getIsSpeed = function (ispeed) {
|
||||||
|
const speedMap = { slow: 8, normal: 20, fast: 40 };
|
||||||
|
return speedMap[ispeed] || 20;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 角度转正弦余弦集合
|
||||||
|
TagCloud._getSc = function (a, b) {
|
||||||
|
const l = Math.PI / 180;
|
||||||
|
return [Math.sin(a * l), Math.cos(a * l), Math.sin(b * l), Math.cos(b * l)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 事件绑定兼容
|
||||||
|
TagCloud._on = function (ele, eve, handler, cap = false) {
|
||||||
|
if (ele.addEventListener) {
|
||||||
|
ele.addEventListener(eve, handler, cap);
|
||||||
|
} else if (ele.attachEvent) {
|
||||||
|
ele.attachEvent("on" + eve, handler);
|
||||||
|
} else {
|
||||||
|
ele["on" + eve] = handler;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 原型方法
|
||||||
|
TagCloud.prototype = {
|
||||||
|
constructor: TagCloud,
|
||||||
|
|
||||||
|
// 获取标签数据(移除缩放相关逻辑)
|
||||||
|
_getItems: function () {
|
||||||
|
const self = this;
|
||||||
|
const items = [];
|
||||||
|
const children = self.box.children;
|
||||||
|
const len = children.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const element = children[i];
|
||||||
|
// 强制设置标签字体大小(固定,不随深度变化)
|
||||||
|
// element.style.fontSize = `${self.fontsize}px`;
|
||||||
|
// element.style.lineHeight = "1.5"; // 固定行高,避免尺寸变化
|
||||||
|
|
||||||
|
const angle = {
|
||||||
|
phi: Math.acos(-1 + (2 * i + 1) / len),
|
||||||
|
theta: Math.sqrt((len + 1) * Math.PI) * Math.acos(-1 + (2 * i + 1) / len)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始3D坐标(球面映射)
|
||||||
|
const x = (self.radius / 2) * 1.5 * Math.cos(angle.theta) * Math.sin(angle.phi) * self.ratioX;
|
||||||
|
const y = (self.radius / 2) * 1.5 * Math.sin(angle.theta) * Math.sin(angle.phi) / self.ratioY;
|
||||||
|
const z = (self.radius / 2) * 1.5 * Math.cos(angle.phi);
|
||||||
|
|
||||||
|
// 彩色标签配置
|
||||||
|
if (self.config.multicolour) {
|
||||||
|
const hue = Math.round(Math.random() * 360);
|
||||||
|
const lightness = 30 + Math.round(Math.random() * 30);
|
||||||
|
element.style.color = `hsl(${hue}, 100%, ${lightness}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签基础样式(移除scale过渡,固定尺寸)
|
||||||
|
element.style.position = "absolute";
|
||||||
|
element.style.transformStyle = "preserve-3d";
|
||||||
|
element.style.transition = "transform 0.3s ease, z-index 0.3s ease, opacity 0.3s ease"; // 仅保留位置、层级、透明度过渡
|
||||||
|
element.style.cursor = "pointer";
|
||||||
|
element.style.transform = "scale(1)"; // 强制固定缩放为1(无放大缩小)
|
||||||
|
element.index = i;
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
element,
|
||||||
|
angle,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z,
|
||||||
|
offsetWidth: element.offsetWidth,
|
||||||
|
offsetHeight: element.offsetHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 绑定交互事件(无缩放逻辑)
|
||||||
|
_bindEvents: function () {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
// 容器鼠标移入/移出
|
||||||
|
TagCloud._on(self.box, "mouseover", () => { self.active = true; });
|
||||||
|
TagCloud._on(self.box, "mouseout", () => { self.active = false; });
|
||||||
|
|
||||||
|
// 鼠标移动
|
||||||
|
const target = self.config.keep ? win : self.box;
|
||||||
|
TagCloud._on(target, "mousemove", (ev) => {
|
||||||
|
const oEvent = win.event || ev;
|
||||||
|
const rect = self.box.getBoundingClientRect();
|
||||||
|
self.mouseX = (oEvent.clientX - (rect.left + rect.width / 2)) / 8;
|
||||||
|
self.mouseY = (oEvent.clientY - (rect.top + rect.height / 2)) / 8;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 标签鼠标移入/移出(仅静止、置顶,无放大)
|
||||||
|
self.items.forEach(item => {
|
||||||
|
TagCloud._on(item.element, "mouseover", function () {
|
||||||
|
self.index = this.index;
|
||||||
|
this.style.zIndex = 999; // 仅置顶,不放大
|
||||||
|
});
|
||||||
|
TagCloud._on(item.element, "mouseout", function () {
|
||||||
|
self.index = -1;
|
||||||
|
this.style.zIndex = "";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成关键帧(移除缩放逻辑,固定scale=1)
|
||||||
|
_generateKeyframe: function (itemIndex) {
|
||||||
|
const self = this;
|
||||||
|
const keyframeName = `tagCloudAnim_${Date.now()}_${itemIndex}`;
|
||||||
|
const styleSheet = doc.createElement("style");
|
||||||
|
let keyframeCSS = `@keyframes ${keyframeName} {`;
|
||||||
|
|
||||||
|
// 拆分360°旋转为20个关键帧
|
||||||
|
for (let deg = 0; deg <= 360; deg += 18) {
|
||||||
|
const rad = deg * Math.PI / 180;
|
||||||
|
const item = self.items[itemIndex];
|
||||||
|
const sc = TagCloud._getSc(
|
||||||
|
Math.sin(rad) * self.mspeed,
|
||||||
|
Math.cos(rad) * self.mspeed
|
||||||
|
);
|
||||||
|
|
||||||
|
// 实时3D坐标计算
|
||||||
|
const rx1 = item.x;
|
||||||
|
const ry1 = item.y * sc[1] + item.z * -sc[0];
|
||||||
|
const rz1 = item.y * sc[0] + item.z * sc[1];
|
||||||
|
const rx2 = rx1 * sc[3] + rz1 * sc[2];
|
||||||
|
const ry2 = ry1;
|
||||||
|
const rz2 = rz1 * sc[3] - rx1 * sc[2];
|
||||||
|
|
||||||
|
// 固定透明度(或按深度微调,不影响尺寸)
|
||||||
|
const per = self.depth / (self.depth + rz2);
|
||||||
|
const opacity = Math.max(0.5, per); // 仅调整透明度,无缩放
|
||||||
|
|
||||||
|
// 容器中心偏移
|
||||||
|
const centerX = (self.box.offsetWidth - item.offsetWidth) / 2;
|
||||||
|
const centerY = (self.box.offsetHeight - item.offsetHeight) / 2;
|
||||||
|
|
||||||
|
// 关键帧内容:固定scale=1,仅保留translate3d和opacity
|
||||||
|
const percent = (deg / 360) * 100;
|
||||||
|
keyframeCSS += `${percent}% {
|
||||||
|
transform: translate(${rx2 + centerX}px, ${ry2 + centerY}px) scale(1);
|
||||||
|
opacity: ${opacity};
|
||||||
|
z-index: ${Math.ceil(per * 10)};
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyframeCSS += `}`;
|
||||||
|
styleSheet.innerHTML = keyframeCSS;
|
||||||
|
doc.head.appendChild(styleSheet);
|
||||||
|
return keyframeName;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成所有标签的关键帧
|
||||||
|
_generateAllKeyframes: function () {
|
||||||
|
const self = this;
|
||||||
|
self.keyframeNames = self.items.map((_, index) => {
|
||||||
|
return self._generateKeyframe(index);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 为标签应用动画(无缩放)
|
||||||
|
_applyAnimations: function () {
|
||||||
|
const self = this;
|
||||||
|
self.items.forEach((item, index) => {
|
||||||
|
const animName = self.keyframeNames[index];
|
||||||
|
// 应用动画:固定scale=1,仅translate3d和opacity变化
|
||||||
|
item.element.style.animation = `${animName} ${self.animDuration}s linear infinite`;
|
||||||
|
item.element.style.transform = `scale(1)`; // 双重保障,防止缩放
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 实时更新(仅静止、调整动画速度,无放大缩小)
|
||||||
|
_update: function () {
|
||||||
|
const self = this;
|
||||||
|
if (self.index === -1 && !self.active) {
|
||||||
|
requestAnimationFrame(self._update);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中标签:仅静止、置顶、保持原始尺寸
|
||||||
|
if (self.index !== -1) {
|
||||||
|
const item = self.items[self.index];
|
||||||
|
const centerX = (self.box.offsetWidth - item.offsetWidth) / 2;
|
||||||
|
const centerY = (self.box.offsetHeight - item.offsetHeight) / 2;
|
||||||
|
// 暂停动画+固定位置+scale=1
|
||||||
|
item.element.style.animationPlayState = "paused";
|
||||||
|
item.element.style.transform = `translate(${centerX}px, ${centerY}px) scale(1)`;
|
||||||
|
item.element.style.opacity = 1;
|
||||||
|
} else {
|
||||||
|
// 鼠标交互:仅调整动画速度,无缩放
|
||||||
|
// self.items.forEach((item, index) => {
|
||||||
|
// item.element.style.animationPlayState = "running";
|
||||||
|
// const newDuration = self.animDuration - Math.abs(self.mouseX + self.mouseY) / 5;
|
||||||
|
// const animName = self.keyframeNames[index];
|
||||||
|
// item.element.style.animation = `${animName} ${Math.max(8, newDuration)}s linear infinite`;
|
||||||
|
// item.element.style.transform = `scale(1)`; // 确保无缩放
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(self._update);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 销毁实例
|
||||||
|
destroy: function () {
|
||||||
|
const self = this;
|
||||||
|
// 移除所有动画样式
|
||||||
|
const styles = doc.querySelectorAll('style');
|
||||||
|
styles.forEach(style => {
|
||||||
|
if (style.innerHTML.includes('tagCloudAnim_')) {
|
||||||
|
doc.head.removeChild(style);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 移除事件监听
|
||||||
|
self.box.onmouseover = null;
|
||||||
|
self.box.onmouseout = null;
|
||||||
|
self.items.forEach(item => {
|
||||||
|
item.element.onmouseover = null;
|
||||||
|
item.element.onmouseout = null;
|
||||||
|
item.element.style.animation = null;
|
||||||
|
item.element.style.transform = "scale(1)";
|
||||||
|
});
|
||||||
|
// 从实例数组中移除
|
||||||
|
const idx = TagCloud.boxs.indexOf(self.box);
|
||||||
|
if (idx !== -1) TagCloud.boxs.splice(idx, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工厂函数
|
||||||
|
return function (options = {}) {
|
||||||
|
const selector = options.selector || ".tagcloud";
|
||||||
|
const elements = doc.querySelectorAll(selector);
|
||||||
|
const instances = [];
|
||||||
|
|
||||||
|
elements.forEach(element => {
|
||||||
|
if (TagCloud.boxs.indexOf(element) === -1) {
|
||||||
|
TagCloud.boxs.push(element);
|
||||||
|
const instance = new TagCloud({ ...options, element });
|
||||||
|
instances.push(instance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return instances;
|
||||||
|
};
|
||||||
|
})(window, document);
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
<!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>
|
|
||||||
Reference in New Issue
Block a user