杨帆 发表于 2026-7-18 21:05

圆 梦

本帖最后由 杨帆 于 2026-7-20 23:08 编辑 <br /><br /><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="referrer" content="never"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&family=Long+Cang&family=Ma+Shan+Zheng&family=ZCOOL+KuaiLe&family=ZCOOL+QingKe+HuangYou&display=swap" rel="stylesheet">
<title>闫学晶 - 圆梦</title>
<style>
#pa {left:calc(50% - 81px);transform:translateX(-50%);margin: 30px auto;width: clamp(600px, 90vw, 1400px);height: auto;aspect-ratio: 16/9;background: url('https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/7eb7acc74bef1b891ca1031a3645309d.jpg') no-repeat center/cover;box-shadow: 2px 2px 8px #000;z-index: 1;overflow: hidden;position: relative;--state:running;}
#player {position: absolute;left:30px;top: 50px;aspect-ratio: 5/3;width: 50px;transform-origin: 100% 100%;animation: rot 6s linear infinite var(--state);cursor: pointer;z-index: 10;}
.petal {position: absolute;inset: 0;background: radial-gradient(circle at 10% 10%, red, orange);border-radius: 0% 90% 0 90%;box-shadow: inset 0 0 30px gold, 2px 4px 16px rgba(0,0,0,.5);transform-origin: 100% 100%;}
.p1 {transform: rotate(0deg);}
.p2 {transform: rotate(72deg);}
.p3 {transform: rotate(144deg);}
.p4 {transform: rotate(216deg);}
.p5 {transform: rotate(288deg);}
#player:hover .petal {transition: transform 0.12s ease;scale:1.1;filter: unset !important;}
#player::after {content: '';position: absolute;top: 100%;left: 100%;width: 10px;height: 10px;border-radius: 50%;background:rgb(248, 243, 235,.75);transform: translate(-50%, -50%);z-index: 10;box-shadow: 0 0 8px Gold;}
.vid {position: absolute;width: 100%;height: 100%;object-fit: cover;mix-blend-mode: screen;mask: linear-gradient(to right top, red 80% ,transparent 90%,transparent);-webkit-mask: linear-gradient(to right top, red 80% ,transparent 90%,transparent);opacity: .45;pointer-events: none;}
@keyframes rot {to { transform: rotate(1turn); }}
#lyricBox{width: 200px;height: calc(100% - 100px);position:absolute;top: 20px;right: 30px;z-index:18;pointer-events:none;}
.char-particle {position: absolute;font-weight: bold;font-family:'Zhi Mang Xing','ZCOOL KuaiLe','Ma Shan Zheng', 'Long Cang','华文琥珀', cursive;will-change: transform, opacity, color;transition: none;font-size: 45px !important;}
.lrc-line {position: absolute; top: 30%;left:calc(50% - 81px);transform:translateX(-50%);font: bold 6.8em SimKai;letter-spacing: 6px;margin: 0;animation:scale 12s ease infinite alternate var(--state);}
::highlight(c1) {color: red;}
::highlight(c2) {color: orange;}
::highlight(c3) {color: magenta;}
::highlight(c4) {color: green;}
::highlight(c5) {color: teal;}
::highlight(c6) {color: blue;}
::highlight(c7) {color: purple;}
@keyframes scale{0%{transform:translateX(-50%) perspective(800px) rotateY(0deg) scale(0.01);}20%{transform:translateX(-50%) perspective(800px) rotateY(0deg) scale(1);}40%{transform:translateX(-50%) perspective(800px) rotateY(-25deg) scale(1.2);}60%{transform:translateX(-50%) perspective(800px) rotateY(25deg) scale(1.2);}80%{transform:translateX(-50%) perspective(800px) rotateY(0deg) scale(1);}100%{transform:translateX(-50%) perspective(800px) rotateY(0deg) scale(0.01);}}
@keyframes rotate{from{transform:rotate(0);}to{transform:rotate(360deg);}}
#fullscreen{ position: absolute;text-align: center; min-width: 80px;border-radius:4px;color:DarkGray;padding:4px 8px;z-index:12;font-size:25px;border:none;cursor:pointer;bottom:25px;left:25px;background:transparent; }
#fullscreen:active {transform: translateY(2px);box-shadow: 0 1px 0 rgba(0,0,0,0.15);}
#fullscreen:hover{ transform: scale(1.1);transition: transform 0.12s ease;color:#007bff;font-weight: bold;}
#statusDisplay { position:absolute;z-index:3;font-size:1.5em;font-weight:50;left:45%;top:15%;color:#fff; }
</style>
</head>
<body>
<div id="pa">
    <div class="lrc-line">圆 梦</div>
    <div id="statusDisplay">播放中: -3:29</div>
    <div id="player-container" style="width:100%;height:100%;position:relative;">
      <div id="lyricBox"></div>
         <div id="fullscreen">全屏欣赏</div>      
      <video class="vid" src="https://img2.tukuppt.com/video_show/2421007/00/01/57/5b46335976efe.mp4" autoplay loop muted></video>
      <video class="vid" src="https://img2.tukuppt.com/video_show/2418175/01/04/62/614ade273f6d1.mp4" autoplay loop muted></video>
      <div id="player" title = "播放/暂停">
            <div class="petal p1"></div>
            <div class="petal p2"></div>
            <div class="petal p3"></div>
            <div class="petal p4"></div>
            <div class="petal p5"></div>
      </div>
    </div>
</div>
<script>
let audioElement = null;
const LRC_STRING = `
圆梦
演唱:闫学晶
为了圆上那个梦
你赌上了一辈子的情
历经艰辛不回头
老牛拉套劲不松
青丝变白发
改不了心中的爱哎
为了圆上那个梦
你赌上了一辈子的情
历经艰辛不回头
老牛拉套劲不松
青丝变白发
改不了心中的爱哎
条条皱纹记下了
苦辣酸甜的风雨行程
嗯嗯哎哎哟
人生就是一场拼争
每天都在攀登
爬上这座山越过那道岭
眼前又是一条河
人人都有一个梦想
每天都在圆这个梦
圆上了爱圆上了情
圆出个无悔无愧的人生
别再让我东张西望
别再让我天天猜想
谁是我的新郎
我是你的新郎
谁是我的新郎
我是你的新郎
哎嗨你快快来到我的身旁
是谁为我穿上嫁妆
是谁伴我走进洞房
谁是我的新郎
我是谁的新娘
哎嗨我是谁的新娘
别再让我东张西望
别再让我天天猜想
你是我的新郎
我是你的新郎
我是你的新娘
你是我的新娘
哎嗨快快去见咱的爹娘
哎嗨快快去见咱的爹娘
`;
const COLOR_A = '#00FFFF';
const COLOR_B = '#4dffff';
const vids = document.querySelectorAll('video');
const statusDisplay = document.getElementById('statusDisplay');
const PARTICLE_CONFIG = {
    maxLineHeight: 720,
    charSpreadBase: 200,
    charSpreadRange: 15,
    charDurationMin: 1300,
    charDurationMax: 1800,
    charDelayMax: 120,
    charScaleMin: 1,
    charRotateMin: -1,
    charRotateMax: 1,
    fadeOutDuration: 450,
    verticalGap: 55,
};
const lyricBox = document.getElementById('lyricBox');
const playerDom = document.getElementById('player');
let LYRICS = [];
let currentParticles = [];
let isTransitioning = false;
let animFrameId = null;
let currentIndex = -1;
let colorLoopId = null;
let audioIsPaused = false;
let lyricThrottleTimer = null;
let pendingLyricIndex = null;
function parseLRC(lrc) {
    const lines = lrc.split('\n');
    const result = [];
    const reg = /\[(\d{2}):(\d{2})\.(\d+)\]/;
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed) continue;
      const match = trimmed.match(reg);
      if (match) {
            const min = +match, sec = +match, ms = +match;
            const time = min * 60 + sec + ms / 1000;
            const text = trimmed.replace(/\[.*?\]/g, '').trim();
            text && result.push({ time, text });
      }
    }
    result.sort((a, b) => a.time - b.time);
    return result;
}
LYRICS = parseLRC(LRC_STRING);
function getRandomColor() {
    return Math.random() > 0.5 ? COLOR_A : COLOR_B;
}
function getTargetPosition(idx, total, centerX, boxHeight) {
    const singleCharH = PARTICLE_CONFIG.verticalGap;
    let totalH = total * singleCharH;
    if (totalH > PARTICLE_CONFIG.maxLineHeight) totalH = PARTICLE_CONFIG.maxLineHeight;
    const singleHeight = totalH / total;
    const startOffsetY = boxHeight / 2 - totalH / 2 + 10;
    const offsetY = idx * singleHeight;
    const tinyYJitter = (Math.random() - 0.5) * PARTICLE_CONFIG.charSpreadRange;
    return {
      x: centerX,
      y: startOffsetY + offsetY + tinyYJitter
    };
}
function createLyricParticles(text, fixedCenterX) {
    const boxRect = lyricBox.getBoundingClientRect();
    const boxH = boxRect.height || lyricBox.clientHeight;
    const charList = text.split('');
    const totalCharNum = charList.length;
    const particleArr = [];
    const fontSize = 36;
    for (let i = 0; i < totalCharNum; i++) {
      const char = charList;
      const isBlank = char === ' ' || char === '\u3000';
      const targetPos = getTargetPosition(i, totalCharNum, fixedCenterX, boxH);
      const animDur = PARTICLE_CONFIG.charDurationMin + Math.random() * (PARTICLE_CONFIG.charDurationMax - PARTICLE_CONFIG.charDurationMin);
      const delayVal = Math.random() * PARTICLE_CONFIG.charDelayMax;
      const rotateVal = PARTICLE_CONFIG.charRotateMin + Math.random() * (PARTICLE_CONFIG.charRotateMax - PARTICLE_CONFIG.charRotateMin);
      const huePhase = Math.random() * Math.PI * 2;
      const span = document.createElement('span');
      span.className = 'char-particle';
      span.textContent = isBlank ? '\u00A0' : char;
      if (isBlank) span.style.opacity = '0';
      span.style.color = getRandomColor();
      span.style.fontSize = fontSize + 'px';
      span.style.left = fixedCenterX + 'px';
      span.style.top = boxH / 2 + 'px';
      span.style.transform = 'translate(-50%, -50%) scale(0.2) rotate(0deg)';
      span.style.opacity = '0';
      span.style.textShadow = '1px 1px 0 #1a0a2e,2px 2px 0 #1a0a2e,3px 3px 0 rgba(26,10,46,0.8),4px 4px 0 rgba(26,10,46,0.6)';
      span.dataset.phase = huePhase;
      lyricBox.appendChild(span);
      particleArr.push({
            el: span,
            char: char,
            isSpace: isBlank,
            startX: fixedCenterX,
            startY: boxH / 2,
            targetX: targetPos.x,
            targetY: targetPos.y,
            targetScale: PARTICLE_CONFIG.charScaleMin,
            targetRotation: rotateVal,
            duration: animDur,
            delay: delayVal,
            startTime: null,
            finished: false,
            fadingOut: false,
            phase: huePhase
      });
    }
    return particleArr;
}
function animateLyricParticles(particleList, animStartStamp, finishCallback) {
    let animStarted = false;
    function renderStep(timeStamp) {
      if (!animStarted) {
            animStarted = true;
            particleList.forEach(p => p.startTime = timeStamp + p.delay);
      }
      let allCharsFinish = true;
      for (const p of particleList) {
            if (p.isSpace || p.fadingOut) continue;
            const timePassed = timeStamp - p.startTime;
            const progress = Math.min(timePassed / p.duration, 1);
            const smoothEase = 1 - Math.pow(1 - progress, 3);
            const moveY = p.startY + (p.targetY - p.startY) * smoothEase;
            const scale = 0.2 + (p.targetScale - 0.2) * smoothEase;
            const rotate = p.targetRotation * smoothEase;
            const opacity = progress < 0.1 ? progress / 0.1 : 1;
            p.el.style.left = p.targetX + 'px';
            p.el.style.top = moveY + 'px';
            p.el.style.transform = `translate(-50%, -50%) scale(${scale}) rotate(${rotate}deg)`;
            p.el.style.opacity = opacity;
            if (progress >= 1) {
                p.finished = true;
                p.el.style.left = p.targetX + 'px';
                p.el.style.top = p.targetY + 'px';
                p.el.style.transform = `translate(-50%, -50%) scale(${p.targetScale}) rotate(${p.targetRotation}deg)`;
                p.el.style.opacity = '1';
            } else {
                allCharsFinish = false;
            }
      }
      if (allCharsFinish) {
            finishCallback?.();
            return;
      }
      animFrameId = requestAnimationFrame(renderStep);
    }
    animFrameId = requestAnimationFrame(renderStep);
}
function fadeOutParticles(particleList, fadeTime, callback) {
    const start = performance.now();
    function step(now) {
      const progress = Math.min((now - start) / fadeTime, 1);
      const ease = 1 - Math.pow(1 - progress, 2);
      for (const p of particleList) {
            if (p.isSpace) continue;
            p.fadingOut = true;
            const alpha = 1 - ease;
            const scale = p.targetScale * (1 - ease * 0.7);
            p.el.style.opacity = alpha;
            p.el.style.transform = `translate(-50%, -50%) scale(${scale}) rotate(${p.targetRotation}deg)`;
      }
      if (progress >= 1) {
            particleList.forEach(p => p.el.parentNode?.removeChild(p.el));
            currentParticles = [];
            callback?.();
            return;
      }
      requestAnimationFrame(step);
    }
    requestAnimationFrame(step);
}
function showLyric(index, callback) {
    if (index < 0 || index >= LYRICS.length) return;
    if (index === currentIndex) return;
    if (isTransitioning) {
      pendingLyricIndex = index;
      return;
    }
    pendingLyricIndex = null;
    isTransitioning = true;
    const boxW = lyricBox.clientWidth || lyricBox.getBoundingClientRect().width;
    const centerX = boxW / 2;
    if (currentParticles.length > 0) {
      fadeOutParticles([...currentParticles], PARTICLE_CONFIG.fadeOutDuration, () => {
            createAndRender(index, centerX, callback);
      });
    } else {
      createAndRender(index, centerX, callback);
    }
}
function createAndRender(index, centerX, callback) {
    const lyricLine = LYRICS;
    const newParticles = createLyricParticles(lyricLine.text, centerX);
    currentParticles = newParticles;
    animateLyricParticles(newParticles, performance.now(), () => {
      isTransitioning = false;
      if (pendingLyricIndex !== null) {
            showLyric(pendingLyricIndex);
      }
      callback?.();
    });
    currentIndex = index;
}
function startColorLoop() {
    colorLoopId && cancelAnimationFrame(colorLoopId);
    let baseTime = performance.now();
    function updateColor(timeStamp) {
      const sec = (timeStamp - baseTime) / 1000;
      const t = (Math.sin(sec * 0.4) + 1) / 2;
      for (const p of currentParticles) {
            if (p.isSpace || !p.el.parentNode) continue;
            const hex = (c) => parseInt(c.slice(1), 16);
            const r1 = hex(COLOR_A) >> 16, g1 = (hex(COLOR_A) >> 8) & 0xff, b1 = hex(COLOR_A) & 0xff;
            const r2 = hex(COLOR_B) >> 16, g2 = (hex(COLOR_B) >> 8) & 0xff, b2 = hex(COLOR_B) & 0xff;
            const r = Math.round(r1 + (r2 - r1) * t);
            const g = Math.round(g1 + (g2 - g1) * t);
            const b = Math.round(b1 + (b2 - b1) * t);
            const nowColor = `rgb(${r},${g},${b})`;
            p.el.style.color = nowColor;
            p.el.style.textShadow = `1px 1px 0 #1a0a2e,2px 2px 0 #1a0a2e,3px 0 rgba(26,10,46,0.8),4px 4px 0 rgba(26,10,46,0.6),0 0 20px ${nowColor},0 0 60px ${nowColor},0 0 100px ${nowColor}`;
      }
      colorLoopId = requestAnimationFrame(updateColor);
    }
    colorLoopId = requestAnimationFrame(updateColor);
}
startColorLoop();
function formatTime(seconds) {
const abs = Math.abs(seconds);
const m = Math.floor(abs/60);
const s = Math.floor(abs%60);
return `${seconds<0?'-':''}${m}:${s<10?'0':''}${s}`;
}
function syncLyric() {
    if (!audioElement || audioIsPaused) return;
    if (lyricThrottleTimer) return;
    const current = audioElement.currentTime;
const remaining = audioElement.duration - current;
statusDisplay.innerHTML = `<span style="color:#CC00FF">播放中:</span><span style="color:#FF0088">${formatTime(-remaining)}</span>`;
    lyricThrottleTimer = setTimeout(() => {
      const curTime = audioElement.currentTime;
      let targetIdx = 0;
      for (let i = 0; i < LYRICS.length; i++) {
            if (LYRICS.time <= curTime) {
                targetIdx = i;
            } else {
                break;
            }
      }
      if (targetIdx !== currentIndex) {
            showLyric(targetIdx);
      }
      lyricThrottleTimer = null;
    }, 200);
}
const songInfo = {
    audioUrl: "https://www.joy127.com/url/155639.mp3",
};
class AudioPlayer {
    constructor() {
      this.audioObj = null;
      this.vids = vids;
      this.init();
    }
    init() {
      this.audioObj = new Audio(songInfo.audioUrl);
      this.audioObj.preload = "auto";
      this.audioObj.loop = true;
      audioElement = this.audioObj;
      this.audioObj.ontimeupdate = syncLyric;
      this.audioObj.onloadeddata = () => {
            this.audioObj.play().catch(e => console.log("自动播放拦截,请点击圆盘"));
            this.vids.forEach(v => v.play());
            playerDom.title = "暂停";
            document.getElementById('pa').style.setProperty('--state','running');
      };
    }
    togglePlay() {
      if (this.audioObj.paused) {
            audioIsPaused = false;
            this.audioObj.play();
            this.vids.forEach(vid=>vid.play());      
            playerDom.title = "暂停";
            statusDisplay.innerHTML = '<span style="color:#CC00FF">播放中</span>';
            document.getElementById('pa').style.setProperty('--state','running');
      } else {
            audioIsPaused = true;
            this.audioObj.pause();
            this.vids.forEach(vid=>vid.pause());
            playerDom.title = "播放";
            statusDisplay.innerHTML = `<span style="color:#CC00FF">已暂停:</span><span style="color:#FF0088">${formatTime(-(this.audioObj.duration - this.audioObj.currentTime))}</span>`;
            document.getElementById('pa').style.setProperty('--state','paused');
      }
    }
}
const player = new AudioPlayer();
playerDom.onclick = () => {
    player.togglePlay();
};
window.addEventListener('load',()=>{
    setTimeout(()=>{
      if(LYRICS.length>0) showLyric(0);
    }, 600);
})
pa.addEventListener('contextmenu', e => e.preventDefault());
const highlightList = [];
['c1','c2','c3','c4','c5','c6','c7'].forEach(name => {
const hl = new Highlight();
highlightList.push(hl);
CSS.highlights.set(name, hl);
});
function clearAllHighlight() {
highlightList.forEach(hl => hl.clear());
}
function colorizeText(element) {
clearAllHighlight();
const walker = document.createTreeWalker(element,NodeFilter.SHOW_TEXT,
    {acceptNode(node) {
      return node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;}      
    }   
);
let textNode;
while ((textNode = walker.nextNode())) {
    const str = textNode.textContent;   
    for (let i = 0; i < str.length; i++) {
      const range = new Range();
      range.setStart(textNode, i);
      range.setEnd(textNode, i + 1);
      const randomIdx = Math.floor(Math.random() * highlightList.length);
      highlightList.add(range);
    }   
}
}
const staticLine = document.querySelector('.lrc-line');
if (staticLine) colorizeText(staticLine);
playerDom.onanimationiteration = () => pa.style.setProperty('filter', `hue-rotate(${30 + Math.random() * 180}deg)`);
const fullscreen = document.getElementById('fullscreen');
const papa = document.getElementById('pa');   
let fs = true,fsTimer;
fullscreen.onclick = ()=>{
if(fs){
    fullscreen.innerText = '退出全屏';
    papa.requestFullscreen();
}else{
    fullscreen.innerText = '全屏欣赏';
    document.exitFullscreen();
}
fs = !fs;
};
papa.addEventListener('mousemove', () => {
clearTimeout(fsTimer);
fullscreen.style.opacity = '1';
fsTimer = setTimeout(() => fullscreen.style.opacity = '0', 2500);
});
</script>
</body>
</html>

杨帆 发表于 2026-7-18 21:15

代码来自小辣椒分享作品,在此表示感谢{:4_204:}

红影 发表于 2026-7-18 21:26

这个帖子里的歌曲很奇怪,好像是多首歌曲合成的?{:4_203:}

杨帆 发表于 2026-7-18 21:31

本帖最后由 杨帆 于 2026-7-18 23:41 编辑

红影 发表于 2026-7-18 21:26
这个帖子里的歌曲很奇怪,好像是多首歌曲合成的?
呵呵,此《圆梦》是闫学晶 Live 原版,对原版衡越《圆梦》歌词作了节选

霜染枫丹 发表于 2026-7-18 22:24

听歌就是串烧的样子,很给人振奋的情绪。感谢分享,扬帆制作快乐!{:4_204:}{:4_190:}

偶然~ 发表于 2026-7-18 22:47

来欣赏杨帆老师的精品佳作

偶然~ 发表于 2026-7-18 22:47

音画唯美

偶然~ 发表于 2026-7-18 22:48

背景歌声动听

偶然~ 发表于 2026-7-18 22:48

辛苦杨帆老师,祝你健康快乐

杨帆 发表于 2026-7-18 23:17

霜染枫丹 发表于 2026-7-18 22:24
听歌就是串烧的样子,很给人振奋的情绪。感谢分享,扬帆制作快乐!

《圆梦》原版是电视剧《刘老根》片尾曲,原唱为 衡越

此为闫学晶现场翻唱 Live 版本,非串烧。谢谢枫丹老师鼓励,祝夏安{:4_187:}

杨帆 发表于 2026-7-18 23:21

本帖最后由 杨帆 于 2026-7-18 23:37 编辑

偶然~ 发表于 2026-7-18 22:47
来欣赏杨帆老师的精品佳作
谢谢偶然~兄弟鼓励。喜欢闫学晶版本的歌词。人人皆有梦,圆梦方能活出无悔人生。

偶然~ 发表于 2026-7-19 00:39

杨帆 发表于 2026-7-18 23:21
谢谢偶然~兄弟鼓励。喜欢闫学晶版本的歌词。人人皆有梦,圆梦方能活出无悔人生。

杨帆老师制作很棒~!祝你健康快乐,灵感不断,福泽绵延!

雨季工作室 发表于 2026-7-19 08:31

欣赏杨帆老师的佳作!

杨帆 发表于 2026-7-19 10:39

雨季工作室 发表于 2026-7-19 08:31
欣赏杨帆老师的佳作!

谢谢雨季兄弟鼓励,祝夏安{:4_204:}

梦江南 发表于 2026-7-19 11:02

这标题文字特效很漂亮,欣赏杨帆的精制音画。{:4_187:}

杨帆 发表于 2026-7-19 11:48

梦江南 发表于 2026-7-19 11:02
这标题文字特效很漂亮,欣赏杨帆的精制音画。

标题文字特效是尝试、学习马老师的CSS自定义高亮,谢谢江南鼓励,祝夏安{:4_204:}

红影 发表于 2026-7-22 19:24

杨帆 发表于 2026-7-18 21:31
呵呵,此《圆梦》是闫学晶 Live 原版,对原版衡越《圆梦》歌词作了节选

原来是这样。
页: [1]
查看完整版本: 圆 梦