《若能许我再少年》
<style>
#bj {position:relative; margin:90px auto 45px calc(50% - 721px); width:1286px; height:720px; overflow:hidden; z-index:1; background:radial-gradient(ellipse farthest-corner at center center, transparent 78%,#000 100%),url('https://up.mediy.cn/wkzp_f4472fcc0622221f3e78917525dd5c1e.gif')no-repeat center/cover,linear-gradient(135deg, #e56420, #c22525, #3d9c31, #000078);letter-spacing: 1px; border:#d0d0d0 15px inset; border-top:#e0e0e0 15px inset; border-bottom:#adadad 15px inset; }
#fullscreen { position: absolute; top: 30px; right:30px;font: normal 2em/0em 楷体;color:#fff; opacity: .3; cursor: pointer; z-index: 111}
#lyricsCanvas {position: absolute; top: 10%; left: 1%; width: 800px; height: 550px; z-index: 2; letter-spacing: 11px; filter:drop-shadow(#ffffff 1px 0 0)drop-shadow(#ffffff 0 1px 0)drop-shadow(#ffffff -1px 0 0) drop-shadow(#ffffff 0 -1px 0); }
.controls-containe{ position: absolute; bottom: 30px; left: 10px; width: 100%; display: flex; justify-content: center; }
.controls { width: 85%; display: flex; align-items: center; gap: 10px; z-index: 4; }
#progress {position:relative; bottom: 8px; flex-grow: 1; width: 100%; height: 4px; -webkit-appearance: none; background-color: transparent; outline: none; }
#progress::-webkit-slider-container { height: 18px;border-radius: 20px; overflow: hidden;}
#progress::-webkit-slider-runnable-track { height: 4px;border-radius: 20px; background: #EEE;}
#progress::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 12px; height: 12px; border-radius: 20px; background: #880000; border: 1px solid transparent; margin-top: -4px; border-image: linear-gradient(#880000,#880000) 0 fill / 4 11 4 0 / 0px 0px 0 2000px;}
.play-btn { width: 90px; height: 30px; background-size: contain; background-repeat: no-repeat; background-position: center; background-color: transparent; border: none; cursor: pointer;font: 400 30px '隶书', sans-serif; color: #00ffff; display: flex; align-items: center; justify-content: center; filter:drop-shadow(#ffffff 1px 0 0)drop-shadow(#ffffff 0 1px 0); }
.time-display {font: 300 20px '华文隶书', sans-serif; color: #eee; min-width: 110px; text-align: center; }
</style>
<div id="bj">
<span id="fullscreen">全屏欣赏</span>
<canvas id="lyricsCanvas" width="850" height="450"></canvas>
<div class="controls-containe">
<div class="controls">
<span id="playBtn" class="play-btn">暂停</span>
<input type="range" id="progress" min="0" max="100" value="0"></input>
<span id="timeDisplay" class="time-display">00:00 / 00:00</span>
</div>
</div>
</div>
<audio id="audio" src="https://up.mediy.cn/wcrnxwzsn_a95310ababb6bd696d378e7d5f608131.mp3" autoplay loop ></audio>
<div style="height:100px;"></div>
<script>
let fs = true;
fullscreen.onclick = () => {
fs ? (fullscreen.innerText = '退出全屏', bj.requestFullscreen()) : (fullscreen.innerText = '全屏欣赏', document.exitFullscreen());
fs = !fs;
};
</script>
<script>
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? '0' + secs : secs}`;
}
function updateTimeDisplay() {
const currentTime = audio.currentTime;
const duration = audio.duration || 0;
timeDisplay.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
}
const dom = {
lyricsCanvas: document.getElementById('lyricsCanvas'),
playBtn: document.getElementById('playBtn'),
progress: document.getElementById('progress'),
timeDisplay: document.getElementById('timeDisplay')
};
const lyricsCtx = dom.lyricsCanvas.getContext('2d');
const lrcText =`《若能许我再少年》
原唱:司徒兰芳
翻唱:寒冬残荷
一声啼哭来人间
生我我生把债还
来去匆匆一场梦
谁能许我再少年
日思柴米夜思钱
负了青春负红颜
春风若有怜花意
可否许我再少年
若能许我再少年
不负青春不负缘
定遵父母金玉言
不让岁月空流转
若能许我再少年
寒窗苦读不偷闲
无人扶我凌云志
我自踏雪登山巅
日思柴米夜思钱
负了青春负红颜
春风若有怜花意
可否许我再少年
若能许我再少年
不负青春不负缘
定遵父母金玉言
不让岁月空流转
若能许我再少年
寒窗苦读不偷闲
无人扶我凌云志
我自踏雪登山巅
谢谢聆听!
`;
function parseLRC() {
return lrcText.split('\n')
.filter(line => line.trim() && line.includes('['))
.map(line => {
const timeMatch = line.match(/\[(\d+):(\d+)\.(\d+)\]/);
if (!timeMatch) return null;
const time = parseInt(timeMatch) * 60 + parseInt(timeMatch) + parseInt(timeMatch)/100;
const text = line.replace(/\[.*?\]/g, '').trim();
return text ? { time, text } : null;
})
.filter(item => item)
.sort((a, b) => a.time - b.time);
}
const lyricsList = parseLRC();
let currentLyricIndex = -1;
let floatingLyrics = [];
class FloatingLyric {
constructor(text) {
this.text = text;
this.x = Math.random() * (dom.lyricsCanvas.width - 100);
this.y = Math.random() * (dom.lyricsCanvas.height - 30);
this.vx = (Math.random() - 0.5) * 0.5;
this.vy = (Math.random() - 0.5) * 0.5;
this.opacity = 0;
this.targetOpacity = 0.8 + Math.random() * 0.2;
this.size = 30 + Math.random() * 7;
this.color = `hsl(${Math.random() * 100}, 100%, 50%)`; //会生成 0-60 之间的随机数,对应色环上红色到黄色的暖色调范围。饱和度固定为100%,表示颜色非常鲜艳饱满。亮度固定为70%,表示颜色比较明亮但不刺眼。
this.life = 0;
this.maxLife = 10;
this.fadeInDuration = 1;
this.fadeOutDuration = 2;
this.isActive = true;
lyricsCtx.font = `${this.size}px 微软雅黑 `;
this.textWidth = lyricsCtx.measureText(this.text).width;
}
update(deltaTime) {
this.life += deltaTime;
this.x += this.vx;
this.y += this.vy;
if (this.x < 10) {
this.x = 10;
this.vx *= -0.8;
} else if (this.x + this.textWidth > dom.lyricsCanvas.width - 10) {
this.x = dom.lyricsCanvas.width - this.textWidth - 10;
this.vx *= -0.8;
}
if (this.y < this.size + 10) {
this.y = this.size + 10;
this.vy *= -0.8;
} else if (this.y > dom.lyricsCanvas.height - 10) {
this.y = dom.lyricsCanvas.height - 10;
this.vy *= -0.8;
}
if (this.life < this.fadeInDuration) {
this.opacity = (this.life / this.fadeInDuration) * this.targetOpacity;
} else if (this.life > this.maxLife - this.fadeOutDuration) {
this.opacity = ((this.maxLife - this.life) / this.fadeOutDuration) * this.targetOpacity;
} else {
this.opacity = this.targetOpacity;
}
}
draw() {
if (this.opacity <= 0) return;
lyricsCtx.save();
lyricsCtx.globalAlpha = this.opacity;
lyricsCtx.font = `${this.size}px 微软雅黑`;
lyricsCtx.fillStyle = '#000';
for (let i = 1; i <= 3; i++) {
lyricsCtx.fillText(this.text, this.x + i, this.y + i);
}
lyricsCtx.fillStyle = this.color;
lyricsCtx.fillText(this.text, this.x, this.y);
lyricsCtx.shadowBlur = 10;
lyricsCtx.shadowColor = this.color;
lyricsCtx.shadowOffsetX = 0;
lyricsCtx.shadowOffsetY = 0;
lyricsCtx.restore();
}
startFadeOut() {
this.maxLife = this.life + this.fadeOutDuration;
this.isActive = false;
}
}
class LastLyric {
constructor(text) {
this.text = text;
this.opacity = 0;
this.size = 40;
this.color = "#fff666";
this.isActive = false;
this.fadeInDuration = 2;
this.fadeOutDuration = 2;
this.life = 0;
this.maxLife = 40;
}
update(deltaTime) {
this.life += deltaTime;
if (!this.isActive) {
this.opacity = 0;
return;
}
if (this.life < this.fadeInDuration) {
this.opacity = (this.life / this.fadeInDuration);
} else if (this.life > this.maxLife - this.fadeOutDuration) {
this.opacity = ((this.maxLife - this.life) / this.fadeOutDuration);
if (this.opacity <= 0) {
this.deactivate();
}
} else {
this.opacity = 1;
}
}
draw() {
if (!this.isActive || this.opacity <= 0) return;
lyricsCtx.save();
lyricsCtx.globalAlpha = this.opacity;
lyricsCtx.font = `bold ${this.size}px 微软雅黑`;
lyricsCtx.textAlign = 'center';
lyricsCtx.textBaseline = '10';
lyricsCtx.fillStyle = '#000';
lyricsCtx.fillText(this.text, dom.lyricsCanvas.width / 2 + 2, dom.lyricsCanvas.height / 2 + 2);
lyricsCtx.fillStyle = this.color;
lyricsCtx.fillText(this.text, dom.lyricsCanvas.width / 2, dom.lyricsCanvas.height / 2);
lyricsCtx.shadowBlur = 15;
lyricsCtx.shadowColor = this.color;
lyricsCtx.restore();
}
activate() {
this.isActive = true;
this.life = 0;
this.opacity = 0;
}
deactivate() {
this.isActive = false;
this.opacity = 0;
this.life = 0;
}
}
const lastLyric = new LastLyric("谢谢聆听!");
let isLastLyricActive = false;
// 核心动画循环
let lastTime = 0;
function animate(timestamp) {
if (audio.paused) {
requestAnimationFrame(animate);
return;
}
const deltaTime = (timestamp - lastTime) / 1000;
lastTime = timestamp;
lyricsCtx.clearRect(0, 0, dom.lyricsCanvas.width, dom.lyricsCanvas.height);
const currentAudioTime = audio.currentTime;
let targetLyricIndex = -1;
for (let i = 0; i < lyricsList.length; i++) {
if (currentAudioTime >= lyricsList.time) {
targetLyricIndex = i;
} else {
break;
}
}
if (targetLyricIndex !== -1 && targetLyricIndex !== currentLyricIndex) {
if (targetLyricIndex !== lyricsList.length - 1) {
lastLyric.deactivate();
isLastLyricActive = false;
}
const targetLyric = lyricsList;
if (targetLyricIndex === lyricsList.length - 1) {
lastLyric.activate();
isLastLyricActive = true;
} else {
const newLyric = new FloatingLyric(targetLyric.text);
const nextLyricTime = lyricsList?.time || (audio.duration || currentAudioTime + 5);
newLyric.maxLife = nextLyricTime - targetLyric.time + 1;
floatingLyrics.push(newLyric);
}
currentLyricIndex = targetLyricIndex;
}
floatingLyrics = floatingLyrics.filter(lyric => {
lyric.update(deltaTime);
lyric.draw();
return lyric.life < lyric.maxLife && lyric.opacity > 0.01;
});
lastLyric.update(deltaTime);
lastLyric.draw();
if (audio.duration) {
const progressPercent = (audio.currentTime / audio.duration) * 100;
dom.progress.value = progressPercent;
updateTimeDisplay();
}
requestAnimationFrame(animate);
}
dom.playBtn.addEventListener('click', () => {
if (audio.paused) {
audio.play().then(() => {
dom.playBtn.textContent = '暂停';
if (isLastLyricActive) {
lastLyric.deactivate();
isLastLyricActive = false;
}
lastTime = performance.now();
requestAnimationFrame(animate);
}).catch(e => console.error('音频播放失败:', e));
} else {
audio.pause();
dom.playBtn.textContent = '播放';
}
});
dom.progress.addEventListener('input', () => {
if (!audio.duration) return;
const targetTime = (dom.progress.value / 100) * audio.duration;
audio.currentTime = targetTime;
currentLyricIndex = -1;
floatingLyrics = [];
lastLyric.deactivate();
isLastLyricActive = false;
updateTimeDisplay();
let targetLyricIndex = -1;
for (let i = 0; i < lyricsList.length; i++) {
if (targetTime >= lyricsList.time) {
targetLyricIndex = i;
} else {
break;
}
}
if (targetLyricIndex !== -1) {
const targetLyric = lyricsList;
if (targetLyricIndex === lyricsList.length - 1) {
lastLyric.activate();
isLastLyricActive = true;
} else {
const newLyric = new FloatingLyric(targetLyric.text);
newLyric.life = targetTime - targetLyric.time;
const nextLyricTime = lyricsList?.time || (audio.duration || targetTime + 5);
newLyric.maxLife = nextLyricTime - targetLyric.time + 1;
if (newLyric.life > newLyric.maxLife) {
newLyric.startFadeOut();
}
floatingLyrics.push(newLyric);
}
}
dom.playBtn.textContent = audio.paused ? '播放' : '暂停';
});
audio.addEventListener('ended', () => {
audio.currentTime = 0;
audio.play();
dom.playBtn.textContent = '暂停';
currentLyricIndex = -1;
floatingLyrics = [];
lastLyric.deactivate();
isLastLyricActive = false;
lastTime = performance.now();
requestAnimationFrame(animate);
});
audio.addEventListener('loadedmetadata', updateTimeDisplay);
audio.play().then(() => {
dom.playBtn.textContent = '暂停';
lastTime = performance.now();
requestAnimationFrame(animate);
}).catch(() => {
console.log('自动播放被阻止,请点击播放按钮开始');
dom.timeDisplay.textContent = '00:00 / --:--';
});
</script>
呵呵,我制作的GIF图图比mp4还大,不知大家能否打得开! 《曲中人》
尘缘似梦几回真,
冷月偏怜独醒身。
一世云烟飘零久,
数声管弦寄故人。
字字凝喉皆血泪,
声声入耳是前尘。
方知歌者非他客,
原是曲中漂泊人。 寒冬残荷 发表于 2025-10-24 15:31
呵呵,我制作的GIF图图比mp4还大,不知大家能否打得开!
可以打开的
略有卡顿
谁能许我再少年
{:4_178:} 能打开,这动图很漂亮,和歌曲意境十分相符。{:4_187:} 歌词同步制作真好,歌词词句是随机位置出来的呢,真神奇。
歌曲也好听。{:4_199:} 寒冬残荷即会唱又会制作漂亮帖子,很赞{:4_187:}
页:
[1]