From 4087c429fa9648a300329eb5d2ad421f978abee0 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RQ919RC\\Pc" <1300399510@qq.com> Date: Mon, 22 Sep 2025 17:49:06 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E9=A1=B5=E9=9D=A2=E5=B8=83=E5=B1=80):?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84=E9=A1=B5=E9=9D=A2=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E5=92=8C=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除未使用的HTML文件和冗余代码 - 调整侧边栏位置和样式 - 优化标签云组件交互和性能 - 更新播放器控件样式和功能 - 改进预览弹窗的背景透明度 - 添加favicon图标 --- 2.html | 117 --------- index.html | 33 +-- song-request-station copy.html | 256 ------------------ song-request-station.html | 197 +++++--------- static/css/index.css | 119 +++++---- static/css/index.less | 135 +++++----- static/css/song-request-station.css | 171 ++++++++++++ static/css/song-request-station.less | 198 ++++++++++++++ static/img/favicon.ico | Bin 0 -> 1150 bytes static/js/song-request-station.js | 343 ++++++++++++++++++++++++ static/js/tagcloud-2.2 - 副本.js | 326 ----------------------- static/js/tagcloud-2.2.js | 3 +- static/js/tagcloud-3.1.js | 380 +++++++++++++++++++++++++++ tag-bubbles copy.html | 151 ----------- 14 files changed, 1302 insertions(+), 1127 deletions(-) delete mode 100644 2.html delete mode 100644 song-request-station copy.html create mode 100644 static/img/favicon.ico create mode 100644 static/js/song-request-station.js delete mode 100644 static/js/tagcloud-2.2 - 副本.js create mode 100644 static/js/tagcloud-3.1.js delete mode 100644 tag-bubbles copy.html diff --git a/2.html b/2.html deleted file mode 100644 index 547e1d0..0000000 --- a/2.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - Document - - - -
-
生日祝福
-
运动健身
-
通勤路上
-
影视配乐
-
助眠放松
-
派对聚会
-
约会浪漫
- -
- - - - diff --git a/index.html b/index.html index 0a05bbc..80ad903 100644 --- a/index.html +++ b/index.html @@ -21,18 +21,6 @@
-
- +
{{ bannerList[pointerIndex]?.desc }}
@@ -66,6 +54,19 @@
+ +
@@ -209,9 +210,9 @@
广州九微科技有限公司 | Copyright 2001-2025 GTER All Rights Reserved | 粤ICP备14050432号
-
- -
+
+ +
diff --git a/song-request-station copy.html b/song-request-station copy.html deleted file mode 100644 index 15ea7e9..0000000 --- a/song-request-station copy.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - 标签气泡动效 - - - - - - -
-
- -
- - -
- - - - - - - - - - - - - - -
-
-
-
-
-
- - - - diff --git a/song-request-station.html b/song-request-station.html index 1c96940..ea2f1c9 100644 --- a/song-request-station.html +++ b/song-request-station.html @@ -7,10 +7,19 @@ - + + + + -
+
+ +
@@ -35,144 +44,58 @@
-
-
+
+
+
+
+
{{ item.tag }}
+
+
+ +
+
+ +
{{ playData?.name }}
+
+ +
+
+ + + + +
+
+
{{ currentTimeFormatted }}
+
+
+
+
+
{{ durationFormatted }}
+
+
+ +
+
+ + + + + + + +
+
{{ volume }}
+
+
+
+
+
+
- + diff --git a/static/css/index.css b/static/css/index.css index 567bc23..708184a 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -44,60 +44,6 @@ background: linear-gradient(180deg, #1e135e 0%, #5241b0 100%); 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 { height: 116px; border-bottom: 1px solid rgba(255, 255, 255, 0.101961); @@ -109,12 +55,13 @@ .content .introduce .box { justify-content: space-between; height: 685px; + position: relative; } .content .introduce .box .info { padding-top: 60px; -} -.content .introduce .box .info .brand { - margin-bottom: 175px; + display: flex; + flex-direction: column; + justify-content: space-between; } .content .introduce .box .info .brand .fill { width: 16px; @@ -132,6 +79,7 @@ } .content .introduce .box .info .award { flex-direction: column; + margin-bottom: 177px; } .content .introduce .box .info .award .title { width: 101px; @@ -221,6 +169,60 @@ z-index: 1; 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 { background-color: #ffffff; height: 420px; @@ -326,6 +328,7 @@ grid-template-columns: repeat(3, 1fr); background-color: #ffffff; border-radius: 20px; + position: relative; } .content .works .mv-box .item { height: 500px; @@ -859,7 +862,7 @@ left: 0; width: 100%; height: 100%; - background-color: #4c4c4c; + background-color: rgba(0, 0, 0, 0.6); z-index: 1001; } .content .preview .close { diff --git a/static/css/index.less b/static/css/index.less index 39ea6dd..15e69d0 100644 --- a/static/css/index.less +++ b/static/css/index.less @@ -48,68 +48,6 @@ 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 { height: 116px; border-bottom: 1px solid rgba(255, 255, 255, 0.101961); @@ -123,10 +61,15 @@ .box { justify-content: space-between; height: 685px; + position: relative; + .info { padding-top: 60px; + display: flex; + flex-direction: column; + justify-content: space-between; .brand { - margin-bottom: 175px; + // margin-bottom: 175px; .fill { width: 16px; @@ -147,6 +90,7 @@ .award { flex-direction: column; + margin-bottom: 177px; .title { width: 101px; 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 { @@ -372,6 +378,7 @@ grid-template-columns: repeat(3, 1fr); background-color: #ffffff; border-radius: 20px; + position: relative; .item { height: 500px; @@ -998,7 +1005,7 @@ left: 0; width: 100%; height: 100%; - background-color: #4c4c4c; + background-color: rgba(0, 0, 0, 0.6); z-index: 1001; .close { diff --git a/static/css/song-request-station.css b/static/css/song-request-station.css index 81f0779..cb036ba 100644 --- a/static/css/song-request-station.css +++ b/static/css/song-request-station.css @@ -354,6 +354,7 @@ font-weight: 650; color: #583a05 !important; background-color: #f19a04 !important; + z-index: 100 !important; } .tag-item.item2 { height: 50px; @@ -386,3 +387,173 @@ color: #62263c !important; 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; +} diff --git a/static/css/song-request-station.less b/static/css/song-request-station.less index 8284c1e..5ce1b8a 100644 --- a/static/css/song-request-station.less +++ b/static/css/song-request-station.less @@ -426,6 +426,7 @@ font-weight: 650; color: rgb(88, 58, 5) !important; background-color: rgba(241, 154, 4, 1) !important; + z-index: 100 !important; } &.item2 { @@ -463,3 +464,200 @@ 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; + } + } + } + } + } + } +} diff --git a/static/img/favicon.ico b/static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ef218ccbbc65ddb161fbc6725903854a03240360 GIT binary patch literal 1150 zcmbW1&ref95XYBj%E1eXiQ(wQ=$~MM2>u`e6ABeEs8L9iNKoWpH1)?pjqzxtUWgih zBq$u9eFchz7D|hj5X8_5RuF7?k3x#JMuba6zs@YY7c?O!9t>$(1>qq@SJUhyl1_S zJ|}xNt=otcfj_`&yeC`to%tH2w~)DSOMH1B#J0Tv#azcj!EDRM$Zz|1@h7dDMa> zZ4U2S6nvobX1aRU;RT<&7}Jm#dX27?AeAVLM8ha{E%QLtieAVZ%7*+9QDhkiBS~$56R(p(ni-_dt z?M5uT3dwz`@d>Ht$I&q)-$alY9z`lLDeGSt2_s7SxXVr^?YU;YKNIC)bC;iz_Z~QV z3CuwKRjS7&_2St6GgfKmUhvmi@!V2nYudKcM1=`DKbv|&aqKRI0rd#y$@%mB7 { + 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"); diff --git a/static/js/tagcloud-2.2 - 副本.js b/static/js/tagcloud-2.2 - 副本.js deleted file mode 100644 index 29beb11..0000000 --- a/static/js/tagcloud-2.2 - 副本.js +++ /dev/null @@ -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) \ No newline at end of file diff --git a/static/js/tagcloud-2.2.js b/static/js/tagcloud-2.2.js index 367b843..417b97c 100644 --- a/static/js/tagcloud-2.2.js +++ b/static/js/tagcloud-2.2.js @@ -104,7 +104,7 @@ window.tagCloud = (function (win, doc) { } self.up = setInterval(function () { self.update(self); - }, 5); + }); } //实例 @@ -170,7 +170,6 @@ window.tagCloud = (function (win, doc) { //滚动初速度 var speedMap = { slow: 10, - slow2: 14, normal: 25, fast: 50, }; diff --git a/static/js/tagcloud-3.1.js b/static/js/tagcloud-3.1.js new file mode 100644 index 0000000..920be2e --- /dev/null +++ b/static/js/tagcloud-3.1.js @@ -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); \ No newline at end of file diff --git a/tag-bubbles copy.html b/tag-bubbles copy.html deleted file mode 100644 index a8dd7ff..0000000 --- a/tag-bubbles copy.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - 标签气泡动效 - - - -

标签气泡动效演示

-
- - - -