亚伦影音工作室 发表于 2026-2-24 18:43

十七八岁花骨朵[测试加载字体]

本帖最后由 亚伦影音工作室 于 2026-2-25 06:42 编辑 <br /><br /><style>
@import url("https://fonts.googleapis.com/css2?family=Ma+Shan+Zheng&display=swap");
#papa { margin: 10px -300px; width: 1280px;height: 720px;background: url(https://pic1.imgdb.cn/item/699d7f0501b9a5550a549c1c.jpg) no-repeat 0px 0px/cover; box-shadow: 2px 2px 8px #000;--state: running;overflow: hidden; z-index: 1;font-family: "华文新魏","Ma Shan Zheng","SimHei", "Arial", "sans-serif";position: relative;}
#bfq{   
      position:absolute;
      width: 450px;
      height:350px;overflow: hidden;
      background:#0000;
transform:scale(.4);bottom: -100px;
   left: -120px;z-index: 20;}
#cp{   
      position:absolute;
      width: 240px;border-radius: 50%;
      height:240px;animation: rotating 6s infinite linear;
      top:18%;background:repeating-radial-gradient(black, black 5px, #1C1C1C 6px, #1C1C1C 7px);
   cursor: pointer;
   left: 12%;z-index: 1;box-shadow:0px 0px 0px 1px #fff,0px 0px 0px 0px #880000;}
@keyframes rotating { to { transform: rotate(360deg); } }
.overlay {
content: '';
left: 50%;
top: 50%; transform: translate(-50%, -50%);
position: absolute;
width: 238px;
height: 238px;
background: linear-gradient(45deg, transparent, 40%, rgba(255,255,255,0.25), 60%, transparent);
border-radius: 50%;
}


.inner {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 100px;
height: 100px;
box-shadow:0px 0px 0px 1px #eee,0px 0px 0px 2px #444;
background:#880000 url('') no-repeat center / cover;

border-radius: 50%;
}
.inner::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 15px;
height: 15px;
background: #ccc;
border-radius: 50%;
}
#cz {position: absolute;
      top:6%; left:45%;z-index: 2;
      width: 150px;background: url('https://pic1.imgdb.cn/item/6688e0dad9c307b7e9a7a3e1.png')no-repeat center/85%;
      height: 300px;
      cursor: pointer;
}

.pink { transform:rotate(5deg);transform-origin: 100% 0%;}
.purple {transform-origin: 80% 0%;margin: -4px -18px;transform:rotate(-9deg);}

#prog {position: absolute;z-index: 8;
      width: 80%;
      height: 2px;background:#ccc;
      cursor: pointer;
         bottom: 46px;
left:11%;
border-radius: 1px;}
#prog-bar {
            height: 100%;
            background: #FF0000;
            width: 0%;
      }

#tmsg {position: absolute;z-index: 8;
      font: normal 12px sans-serif;
      color: #ccc;
         bottom: 42px;
      right: 40px;}

.lyrics{margin: 0;z-index: 20;
            top: 89%;
            left: 50%;
    transform: translate(-50%, -50%);
            height: 100px; /* 调整高度,只容纳当前歌词 */
                 text-align: center;
             position: absolute;animation:flash 1s linear infinite var(--state);
      }
            .lyric-line{
            width: 100%;
            position: relative;
            height: 60px;
            overflow: visible;font-size: 44px;
   font-width: 400 ;
            line-height: 60px;
         text-align: left;
            white-space: nowrap; /* 禁止换行 */
          filter: drop-shadow(#fff 1px 0 0) drop-shadow(#fff 0 1px 0) drop-shadow(#fff -1px 0 0) drop-shadow(#fff 0 -1px 0);
      }

      .lyric-mask {
            position: absolute;
            top: 0;
            left: 0;
            width: 0;
            overflow: hidden;
          color: #ff0000;

            height: 100%;
            white-space: nowrap;
      }

      .lyric-original {
             color: #00aa00;
            white-space: nowrap;
            
      }   
      
#wrapper {z-index: 9;
                position: absolute; writing-mode: vertical-lr;white-space: pre; width: 60px;height: 90%;top: 90px; left: 15%;
                padding: 0px; display: none;
                font-size: 44px;
   font-width: 400 ;
                filter: drop-shadow(#fff 1px 0 0) drop-shadow(#fff 0 1px 0) drop-shadow(#fff -1px 0 0) drop-shadow(#fff 0 -1px 0);
      
        }
.char {
                display: inline-block;
                padding: 2px 2px;
                opacity: 0;
                transform: translate(var(--x), var(--y));
                animation: fadeIn 1.5s var(--delay) forwards, flash 1s infinite linear var(--state);
        }
       
        @keyframes fadeIn {
                to {
                        transform:scale(1);
                        opacity: 1;
                }
        }
        @keyframes flash {
                to { filter: hue-rotate(360deg)brightness(160%); }
        }
       
        #g1,#g2,#fullscreen{border-radius: 2px;position:relative;z-index: 1111;
color:#000;background:#0000;box-shadow:0px 0px 0px 1px #000;family: "Ma Shan Zheng","华文行楷","SimHei", "Arial", "sans-serif";
padding: 4px 8px;
font-size: 16px;
border: none;
cursor: pointer;margin: 8px 5px;
}


</style>
    <div id="papa">
      
      <audio id="aud" src="https://s2.cldisk.com/sv-w7/audio/de/6a/ee/cffdf57b608304a017030999080f2a46/audio.mp3" autoplay loop></audio>
      <div id="bfq">
<div id="cz"class="pink"title="暂停"></div>
<div id="cp"title="暂停"><div class="inner"></div><div class="overlay"></div></div>
</div>
<div style="position: absolute;top:20px;left:40%;">
<span id="g1" onclick="btn1()"title="竖排歌词">竖排歌词</span>
<span id="g2" onclick="btn2()"title="横排歌词">横排歌词</span>
<span id="fullscreen" title="屏展模式">全屏欣赏</span>
</div>
<div id="prog" title="播放进度条"><div id="prog-bar"></div></div>
<div id="tmsg">00:00|00:00</div>

      <div id="wrapper">小妹</div>

    <div class="lyrics" >
            <div class="lyric-line">
                  <div class="lyric-mask"></div>
                  <div class="lyric-original"></div>
                </div>
            </div>
      
    </div>
<script>
    const lrc = `十七八岁花骨朵
演唱/作词/作曲:小田儿
制作:亚伦影音工作室
十七十八花正开
好花盼着哥来采
若是哥哥你不来
再美花儿也白开
妹是山茶花骨朵
风吹花香迷倒哥
好花无人采回去
急的小妹眼泪落
好花开在小河旁
遍地都是花芬芳
水长路远无人赏
逗得妹妹心彷徨
好花开在高山巅
彩蝶飞舞绕花间
好花需要蜂采蜜
妹妹盼哥来相伴

`;
const aud= document.getElementById('aud');
      const lyrics = parseLyrics(lrc);
      const lyricMask = document.querySelector('.lyric-mask');
      const lyricOriginal = document.querySelector('.lyric-original');
      
      let currentIndex = -1;
      let currentLyric = null;
      
      // 解析歌词(支持两种格式)
      function parseLyrics(lrcText) {
            const lyrics = [];
            if (lrcText.includes('karaoke.add')) {
                const lineRegex = /karaoke\.add\('([^']+)', '([^']+)', '([^']+)', '([^']+)'\);/g;
                let match;
                while ((match = lineRegex.exec(lrcText)) !== null) {
                  const startTime = timeToMs(match);
                  const endTime = timeToMs(match);
                  const text = match.replace(/\[|\]/g, '').trim();
                  const durations = match.split(',').map(Number);
                  if (text) {
                        lyrics.push({startTime, endTime, text, durations});
                  }
                }
            }
            else if (lrcText.includes('[')) {
                const lines = lrcText.split('\n').filter(line => line.trim());
                lines.forEach((line, index) => {
                  const timeMatch = line.match(/\[(\d+:\d+\.\d+)\]/);
                  if (timeMatch) {
                        const timeStr = timeMatch;
                        const text = line.replace(/\[.*?\]/, '').trim();
                        if (text) {
                            const startTime = timeToMs(timeStr);
                            const nextLine = lines;
                            const nextTimeMatch = nextLine ? nextLine.match(/\[(\d+:\d+\.\d+)\]/) : null;
                            const endTime = nextTimeMatch ? timeToMs(nextTimeMatch) : startTime + 5000;
                            lyrics.push({
                              startTime,
                              endTime,
                              text,
                              durations: calculateCharDurations(text, startTime, endTime)
                            });
                        }
                  }
                });
            }
            return lyrics;
      }
      function calculateCharDurations(text, startTime, endTime) {
            const totalDuration = endTime - startTime;
            const charCount = text.length;
            const baseDur = Math.floor(totalDuration / charCount);
            const durations = new Array(charCount).fill(baseDur);
            const remainder = totalDuration % charCount;
            for (let i = 0; i < remainder; i++) {
                durations++;
            }
            return durations;
      }
      function timeToMs(timeStr) {
            const parts = timeStr.split(':');
            const minutes = parseInt(parts, 10);
            const secondsAndMs = parts.split('.');
            const seconds = parseInt(secondsAndMs, 10);
            const ms = parseInt(secondsAndMs || 0, 10);
            return minutes * 60 * 1000 + seconds * 1000 + ms;
      }
      function getCurrentLyricIndex(lyrics, currentTimeMs) {
            for (let i = 0; i < lyrics.length; i++) {
                if (currentTimeMs >= lyrics.startTime && currentTimeMs <= lyrics.endTime) {
                  return i;
                }
            }
            return -1;
      }
      function updateLyricDisplay(index) {
            if (index < 0 || index >= lyrics.length) return;
            currentIndex = index;
            currentLyric = lyrics;
            lyricOriginal.textContent = currentLyric.text;
            lyricMask.textContent = currentLyric.text;
            lyricMask.style.width = '0%';
      }
      function updateLyricMask(currentTimeMs) {
            if (!currentLyric) return;
            const lyricStartTime = currentLyric.startTime;
            const elapsed = currentTimeMs - lyricStartTime;
            const totalDuration = currentLyric.durations.reduce((sum, d) => sum + d, 0);
            let charIndex = 0;
            let accumulatedTime = 0;
            
            for (let i = 0; i < currentLyric.durations.length; i++) {
                accumulatedTime += currentLyric.durations;
                if (elapsed <= accumulatedTime) {
                  charIndex = i + 1;
                  break;
                }
            }
            
            if (elapsed >= totalDuration) {
                charIndex = currentLyric.text.length;
            }
            
            charIndex = Math.min(charIndex, currentLyric.text.length);
            
            const tempSpan = document.createElement('span');
            tempSpan.style.visibility = 'hidden';
            tempSpan.style.position = 'absolute';
            tempSpan.style.fontSize = '50px';
            tempSpan.style.fontWeight = '300';
            document.body.appendChild(tempSpan);
            
            const visibleText = currentLyric.text.substring(0, charIndex);
            tempSpan.textContent = visibleText;
            const width = tempSpan.offsetWidth;
            document.body.removeChild(tempSpan);
            
            lyricMask.style.width = `${width}px`;
      }
      
      // 监听更新歌词
      aud.addEventListener('timeupdate', () => {
            const currentTimeMs = aud.currentTime * 1000;
            const index = getCurrentLyricIndex(lyrics, currentTimeMs);
            
            if (index !== currentIndex) {
                updateLyricDisplay(index);
            }
            
            updateLyricMask(currentTimeMs);
      });
      updateLyricDisplay(0);



const gcAr = lrc2HC(lrc);
        let curkey = 0, isSeeking = false;

        aud.ontimeupdate = () => {
                if(curkey > gcAr.length - 1) return;
                if(aud.currentTime >= gcAr) {
                        const gap = gcAr?. ?? 0 - gcAr;
                        showLrc(gcAr, wrapper, gap);
                }
        };

        aud.onended = () => {
                curkey = 0;
                aud.play();
        }

        aud.onseeked = () => calcKey();

        function lrc2HC(text) {
                let lrcAr = [];
                let ar = text.trim().split('\n');
                ar.sort();
                let reg = /\[(\d+)[.:](\d+)[.:](\d+)\](.*)/;
                ar.forEach(item => {
                        if(reg.test(item)) {
                                let result = item.match(reg);
                                let tmsg = parseInt(result) * 60 + parseInt(result) + parseInt(result) / 1000;
                                lrcAr.push(.trim()]);
                        }
                });
                return lrcAr ? lrcAr : ;
        };

        function calcKey() {
                for (let j = 0; j < gcAr.length; j++) {
                        if (aud.currentTime <= gcAr) {
                                curkey = j - 1;
                                break;
                        }
                }
                if (curkey < 0) curkey = 0;
                if (curkey > gcAr.length - 1) curkey = gcAr.length - 1;
                let time = gcAr?. ?? 0 - gcAr;
                isSeeking = false;
                showLrc(gcAr, wrapper, time);
        }

        function showLrc(str, targetElm, time) {
                if(isSeeking) return;
                targetElm.innerHTML = '';
                const chars = str.split('').map(c => c === ' ' ? ' ' : c);
                const frg = document.createDocumentFragment();
                chars.forEach((char, idx) => {
                        const span = document.createElement('span');
                        span.innerHTML = char;
                        span.classList.add('char');
                        const x = Math.random() * (Math.random() > 0.5 ? 600 : -300);
                        const y = Math.random() * (Math.random() > 0.5 ? 600 : -300);
                        span.style.cssText += `
                                color: #${Math.random().toString(16).substring(2,8)};
                                --x: ${x}px;
                                --y: ${y}px;
                                --delay: ${Math.random() * 0.5}s;
                        `;
                        frg.appendChild(span);
                });
                targetElm.appendChild(frg);
                curkey ++;
                setTimeout(() =>isSeeking = false, time);}

</script>

      
<script>
aud.addEventListener('timeupdate', () => {
            tmsg.innerText = toMin(aud.currentTime) + ' | ' + toMin(aud.duration);
      });
      
      function toMin(val) {
            if (!val) return '00:00';
            val = Math.floor(val);
            let min = parseInt(val / 60);
            let sec = parseFloat(val % 60);
            if (min < 10) min = '0' + min;
            if (sec < 10) sec = '0' + sec;
            return min + ':' + sec;
      }
      
      prog.onclick = (e) => { aud.currentTime = aud.duration * e.offsetX / prog.offsetWidth; }
      
      var progBar = document.getElementById('prog-bar');
      aud.addEventListener('timeupdate', () => {
            const percent = (aud.currentTime / aud.duration) * 100;
            progBar.style.width = percent + '%';
      });
      

cp.onclick = cz.onclick = () => aud.paused ? (aud.play(), cz.classList.remove('purple')) : (aud.pause(), cz.classList.add('purple'));
      
      cp.style.animationPlayState = aud.paused ? 'paused' : 'running';
      aud.addEventListener('playing', () => cp.style.animationPlayState = 'running');
      aud.addEventListener('pause', () => cp.style.animationPlayState = 'paused');
let mState = () => papa.style.setProperty('--state',aud.paused ? 'paused' : 'running');
aud.onplaying = aud.onpause = () => mState();

    </script>

<script type="module">
      import { THREE, scene, camera, renderer, clock, basic3, click3 } from 'https://638183.freep.cn/638183/3dev/3/3basic.js?v=1.0';
      import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
      import { FS } from 'https://638183.freep.cn/638183/web/ku/FS.js';
      basic3(papa, aud.paused);

      // 绘制点云球体 :点颜色随机
      const geometry = new THREE.SphereGeometry(0.8, 64, 64);
      const count = geometry.attributes.position.count;
      const colors = new Float32Array(count * 3);
      for (let i = 0; i < count; i++) {
            colors = Math.random();
            colors = Math.random();
            colors = Math.random();
      }
      geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
      const material = new THREE.PointsMaterial({ size: 0.05, vertexColors: true });
      const mesh = new THREE.Points(geometry, material);
      scene.add(mesh);
      // 加入 tween 动画 :点从球体飞往各处再折回
      const startPositions = geometry.getAttribute('position');
      for(let i = 0; i < startPositions.count; i++) {
                if (i % 5 === 0) continue; // 排除索引被5整除的点
                const tween = new TWEEN.Tween(startPositions.array)
                .to({
                        : THREE.MathUtils.randFloatSpread(10),
                        : THREE.MathUtils.randFloatSpread(10),
                        : THREE.MathUtils.randFloatSpread(10)
                }, 5000 * Math.random() + 6000)
                .delay(2000)
                .easing(TWEEN.Easing.Exponential.Out)
                .onUpdate(() => startPositions.needsUpdate = true)
                .repeat(Infinity)
                .repeatDelay(1500)
                .yoyo(6000)
                .start();
      }

      const animate = () => {
                TWEEN.update();
                const delta = clock.getDelta();
                mesh.rotation.x += delta / 5;
                mesh.rotation.y += delta / 5;
                renderer.render(scene, camera);
                requestAnimationFrame(animate);
      };

      papa.onclick = (e) => {
                if (click3(mesh, e)) player.click();
      };

      papa.onmousemove = (e) => {
                papa.title = click3(mesh, e) ? '播放/暂停(Alt+X)' : '';
                papa.style.cursor = click3(mesh, e) ? 'pointer' : 'default';
      };

      const tweens = TWEEN.getAll();

      aud.onplaying = aud.onpause = () => {
                tweens.forEach(tween => aud.paused ? tween.pause() : tween.resume());
                aud.paused ? clock.stop() : clock.start();
      };

      animate();
      FS(papa, player);
</script>
<script>
var g1=document.getElementById("wrapper");

const g2 = document.querySelector('.lyrics');
function btn1() {
            
            g1.style.display = 'block';
            g2.style.display = 'none';
      }

      function btn2() {
         g1.style.display = 'none';
            g2.style.display = 'block';
      }
let fs = true;
      fullscreen.onclick = () => {
            if (fs) {
                fullscreen.innerText = '退出全屏';
                papa.requestFullscreen();
            } else {
                fullscreen.innerText = '全屏欣赏';
                document.exitFullscreen();
            }
            fs = !fs;
      };


</script>

亚伦影音工作室 发表于 2026-2-24 18:43

本帖最后由 亚伦影音工作室 于 2026-2-24 16:54 编辑 <br /><br />1

亚伦影音工作室 发表于 2026-2-24 18:43

本帖最后由 亚伦影音工作室 于 2026-2-24 18:48 编辑 <br /><br />我的地盘

红影 发表于 2026-2-24 19:55

横排歌词和竖排歌词切换很自如,欣赏亚伦老师好帖{:4_199:}

杨帆 发表于 2026-2-26 12:39

切换顺畅,画面精彩,视听盛宴,谢谢亚伦老师精彩分享{:4_190:}
页: [1]
查看完整版本: 十七八岁花骨朵[测试加载字体]