盗版《笑红尘》源码来自了了仔
本帖最后由 亚伦影音工作室 于 2026-6-25 19:28 编辑 <br /><br /><style>@import url("https://fonts.googleapis.com/css2?family=Liu+Jian+Mao+Cao&family=Long+Cang&family=Ma+Shan+Zheng&family=ZCOOL+KuaiLe&family=ZCOOL+QingKe+HuangYou&display=swap");
#bj {
position: relative;
width: 1286px;
height: 710px;
margin: 0 -330px;
overflow: hidden;
border-radius: 2px;
box-shadow: 0px 0px 1px 1px #b896ff;
background:url(https://pic1.imgdb.cn/item/6a3ca16752bf8ed03f85efbf.png) no-repeat center/cover; /* 备用背景色 */
}
.containe{
position:absolute;width: 100%;height: 100%;left: 0%;top: 0%;background: url(https://pic1.imgdb.cn/item/6a3ca16752bf8ed03f85efbf.png) no-repeat center/cover;}
.filtered-background, .main-background {
position: absolute;
height: 100%;
width: 100%;clip-path: polygon(0% 0%, 0% 100%, 50% 100%, 35% 0, 68% 0, 40% 100%, 100% 100%, 100% 0%);
background:url(https://pic1.imgdb.cn/item/6a3ca16752bf8ed03f85efbf.png) no-repeat center/cover;
}
.filtered-background {
filter: url("#turbulence");
}
#lyrics-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 5;
pointer-events: none;
}
.char-particle {
position: absolute;
font-weight: 100px;
font-family: 'Ma Shan Zheng', 'ZCOOL KuaiLe', 'Long Cang', '华文琥珀', cursive;
color: #fff000;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);
transition: none;
}
.song-artist {
position: absolute;
bottom: 20px;
right: 20px;
font-size: 18px;
color: #7a5cbf;
z-index: 15;
pointer-events: none;
background: transparent;
padding: 4px 12px;
}
.spectrum-player {
position: absolute;
z-index: 30;
left: 5%;
bottom: 120px;
width: 110px;
height: 110px;
cursor: pointer;
user-select: none;
}
.progress-ring-svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.progress-ring-circle {
fill: none;
stroke: #b896ff;
stroke-width: 2.5;
stroke-linecap: round;
stroke-dasharray: 238.76;
stroke-dashoffset: 238.76;
transition: stroke-dashoffset 0.08s linear;
}
#fireworksCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 31;
}
.spectrum-dots {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
transform: translate(-3px, -3px);
}
.spectrum-dot {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
background: #b896ff;
box-shadow: 0 0 4px #b896ff, 0 0 2px #9b7bff;
transform-origin: center;
transition: transform 0.05s, background 0.05s, box-shadow 0.05s;
}
.center-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 70px;
height: 70px;
border-radius: 50%;
background: rgba(0,0,0,0.65);
backdrop-filter: blur(8px);
border: 1px solid rgba(184,150,255,0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 5;
font-family: 'Microsoft YaHei', monospace;
text-align: center;
color: #b896ff;
text-shadow: 0 0 2px #6d4c9a;
line-height: 1.2;
box-shadow: 0 0 20px rgba(184,150,255,0.3);
transition: all 0.2s;
}
.center-btn:hover {
background: rgba(0,0,0,0.8);
border-color: #b896ff;
transform: translate(-50%, -50%) scale(1.02);
}
.center-title { font-size: 14px; font-weight: bold; letter-spacing: 2px; }
.time-remain { font-size: 12px; font-family: monospace; margin-top: 2px; }
.decoration {
position: absolute;
z-index: 0;
pointer-events: none;
}
.decoration-1 {
top: 10px;
left: 10px;
width: 100px;
height: 100px;
background: radial-gradient(circle, #b896ff, transparent 70%);
opacity: 0.2;
border-radius: 50%;
}
.decoration-2 {
bottom: 10px;
right: 10px;
width: 80px;
height: 80px;
background: radial-gradient(circle, #b896ff, transparent 70%);
opacity: 0.2;
border-radius: 50%;
}
</style>
<div id="bj">
<canvas id="bgCanvas"></canvas>
<div class="containe">
<div class="filtered-background"></div>
</div>
<svgid="reflect" >
<filter id="turbulence" x="0%" y="0%" width="100%" height="100%" >
<feTurbulence id="ripples" numOctaves="6" seed="6" baseFrequency="0.02 0.05"></feTurbulence>
<feDisplacementMap scale="8" in="SourceGraphic"></feDisplacementMap>
<animate xlink:href="#ripples" attributeName="baseFrequency" dur="30s" keyTimes="0;0.5;1"
values="0.02 0.05;
0.04 0.07;
0.02 0.05;" repeatCount="indefinite"/>
</filter>
</svg>
<div id="lyrics-container"></div>
<div class="spectrum-player" id="spectrumPlayer">
<canvas id="fireworksCanvas"></canvas>
<svg class="progress-ring-svg" viewBox="0 0 110 110">
<circle class="progress-ring-circle" cx="55" cy="55" r="38"></circle>
</svg>
<div class="spectrum-dots" id="spectrumDots"></div>
<div class="center-btn" id="centerBtn">
<div class="center-title">笑红尘</div>
<div class="time-remain" id="btnTimeRemain">-00:00</div>
</div>
</div>
<div class="loading" id="loading">加载中...</div>
<div class="decoration decoration-1"></div>
<div class="decoration decoration-2"></div>
<audio id="audio" loop src="http://music.163.com/song/media/outer/url?id=3396958938.mp3"></audio>
</div>
<script>
const BG_BRIGHTNESS = 1.2;
const BG_CONTRAST = 1.1;
const BG_OPACITY = 1;
const TOTAL_FRAMES = 87;
const FRAME_DURATION_MS = 100;
const FRAME_BASE_PATH = 'llz/';
const FRAME_EXT = '.jpg';
function getFrameUrl(index) {
const num = (index + 1).toString().padStart(3, '0');
return `${FRAME_BASE_PATH}llz${num}${FRAME_EXT}`;
}
const canvasBg = document.getElementById('bgCanvas');
const ctxBg = canvasBg.getContext('2d');
let frames = [];
let framesLoaded = false;
let bgAnimId = null;
let bgPlaying = false;
function loadFrames() {
let loadedCount = 0;
for (let i = 0; i < TOTAL_FRAMES; i++) {
const img = new Image();
img.src = getFrameUrl(i);
img.onload = () => {
loadedCount++;
if (loadedCount === TOTAL_FRAMES) {
framesLoaded = true;
console.log(`所有帧加载完成,共 ${TOTAL_FRAMES} 帧`);
if (frames) {
canvasBg.width = frames.width;
canvasBg.height = frames.height;
}
drawFrame(0);
requestAnimationFrame(updateBgFrame);
}
};
img.onerror = (e) => console.error(`帧图片加载失败: ${img.src}`, e);
frames.push(img);
}
}
function drawFrame(index) {
if (!framesLoaded || !frames || !frames.complete) return;
ctxBg.clearRect(0, 0, canvasBg.width, canvasBg.height);
ctxBg.filter = `brightness(${BG_BRIGHTNESS}) contrast(${BG_CONTRAST})`;
ctxBg.globalAlpha = BG_OPACITY;
ctxBg.drawImage(frames, 0, 0, canvasBg.width, canvasBg.height);
// ctxBg.filter = 'none';
// ctxBg.globalAlpha = 1.0;
}
function updateBgFrame() {
if (!framesLoaded) {
bgAnimId = requestAnimationFrame(updateBgFrame);
return;
}
const audio = document.getElementById('audio');
if (audio && !audio.paused && !audio.ended) {
const currentTime = audio.currentTime;
const totalCycleDuration = (TOTAL_FRAMES * FRAME_DURATION_MS) / 1000; // 8.7秒
const elapsed = currentTime % totalCycleDuration;
const frameIndex = Math.floor(elapsed / (FRAME_DURATION_MS / 1000)) % TOTAL_FRAMES;
drawFrame(frameIndex);
bgPlaying = true;
} else {
bgPlaying = false;
}
bgAnimId = requestAnimationFrame(updateBgFrame);
}
loadFrames();
const LRC_STRING = `
笑红尘*红尘多可笑*痴情最无聊
目空一切也好*此生未了
心却已无所扰
只想换得半世逍遥
醒时对人笑
梦中全忘掉
叹天黑得太早来生难料
爱恨一笔勾销
对酒当歌我只愿开心到老
风再冷不想逃
花再美也不想要任我飘摇
天越高心越小
不问因果有多少独自醉倒
今天哭明天笑
不求有人能明了一身骄傲
歌在唱舞在跳
长夜漫漫不觉晓将快乐寻找
红尘多可笑痴情最无聊
目空一切也好此生未了
心却已无所扰
只想换得半世逍遥
醒时对人笑梦中全忘掉
叹天黑得太早来生难料
爱恨一笔勾销
对酒当歌我只愿开心到老
风再冷不想逃
花再美也不想要任我飘摇
天越高心越小
不问因果有多少独自醉倒
今天哭明天笑
不求有人能明了一身骄傲
歌在唱舞在跳
长夜漫漫不觉晓将快乐寻找/name/.
`;
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 = parseInt(match, 10);
const sec = parseInt(match, 10);
const ms = parseInt(match, 10);
const time = min * 60 + sec + ms / 1000;
const text = trimmed.replace(/\[.*?\]/g, '').trim();
if (text) result.push({ time, text });
}
}
result.sort((a, b) => a.time - b.time);
return result;
}
const LYRICS = parseLRC(LRC_STRING);
console.log('🎵 歌词加载完成,共 ' + LYRICS.length + ' 句');
const COLORS = ['#00aa00','#9b7bff','#880055','#a67cff','#000000','#6d4c9a','#ff0000','#880000','#000078','#7f5fc4','#000090','#9f80e6'];
const PARTICLE_CONFIG = {
charSpreadBase: 290,
charSpreadRange: 60,
charAngleJitter: 0.05,
charDurationMin: 1200,
charDurationMax: 2000,
charDelayMax: 180,
charScaleMin: 0.6,
charScaleMax: 1.4,
charRotateMin: -20,
charRotateMax: 20,
fadeOutDuration: 400,
};
const container = document.getElementById('lyrics-container');
let currentParticles = [];
let isTransitioning = false;
let animFrameId = null;
let currentIndex = 0;
let colorLoopId = null;
function getRandomColor() { return COLORS; }
function getTargetPosition(idx, total, cx, cy) {
const baseAngle = (idx / total) * 2 * Math.PI;
const jitter = (Math.random() - 0.5) * PARTICLE_CONFIG.charAngleJitter * 2;
const angle = baseAngle + jitter;
const radius = PARTICLE_CONFIG.charSpreadBase + (Math.random() - 0.5) * PARTICLE_CONFIG.charSpreadRange;
return { x: cx + Math.cos(angle) * radius, y: cy + Math.sin(angle) * radius * 0.9 };
}
function createLyricParticles(text, cx, cy) {
const chars = text.split('');
const total = chars.length;
const particles = [];
const baseFontSize = Math.min(window.innerWidth, window.innerHeight) * 0.06;
for (let i = 0; i < total; i++) {
const char = chars;
const isSpace = char === ' ' || char === '\u3000';
const target = getTargetPosition(i, total, cx, cy);
const duration = PARTICLE_CONFIG.charDurationMin + Math.random() * (PARTICLE_CONFIG.charDurationMax - PARTICLE_CONFIG.charDurationMin);
const delay = Math.random() * PARTICLE_CONFIG.charDelayMax;
const scale = PARTICLE_CONFIG.charScaleMin + Math.random() * (PARTICLE_CONFIG.charScaleMax - PARTICLE_CONFIG.charScaleMin);
const rotation = PARTICLE_CONFIG.charRotateMin + Math.random() * (PARTICLE_CONFIG.charRotateMax - PARTICLE_CONFIG.charRotateMin);
const color = getRandomColor();
const fontSize = baseFontSize * (0.85 + Math.random() * 0.3);
const phase = Math.random() * 2 * Math.PI;
const el = document.createElement('span');
el.className = 'char-particle';
el.textContent = isSpace ? '\u00A0' : char;
if (isSpace) { el.style.opacity = '0'; el.style.pointerEvents = 'none'; }
el.style.color = color;
el.style.fontSize = fontSize + 'px';
el.style.left = cx + 'px';
el.style.top = cy + 'px';
el.style.transform = 'translate(-50%, -50%) scale(0.2) rotate(0deg)';
el.style.opacity = '0';
el.style.zIndex = isSpace ? 0 : 2;
el.dataset.phase = phase;
container.appendChild(el);
particles.push({
el, char, isSpace,
startX: cx, startY: cy,
targetX: target.x, targetY: target.y,
targetScale: scale, targetRotation: rotation,
duration, delay, color,
startTime: null, finished: false, fadingOut: false,
phase: phase
});
}
return particles;
}
function animateLyricParticles(particles, startTime, callback) {
let started = false;
function step(timestamp) {
if (!started) {
started = true;
for (const p of particles) p.startTime = timestamp + p.delay;
}
let allFinished = true;
for (const p of particles) {
if (p.isSpace) { p.el.style.opacity = '0'; continue; }
if (p.fadingOut) continue;
const elapsed = timestamp - p.startTime;
const progress = Math.min(elapsed / p.duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
const x = p.startX + (p.targetX - p.startX) * ease;
const y = p.startY + (p.targetY - p.startY) * ease;
const scaleVal = 0.2 + (p.targetScale - 0.2) * ease;
const overshoot = 1 + 0.12 * Math.sin(ease * Math.PI) * (1 - ease);
const finalScale = scaleVal * overshoot;
const rot = p.targetRotation * ease;
let alpha = progress < 0.1 ? progress / 0.1 : 1;
p.el.style.left = x + 'px';
p.el.style.top = y + 'px';
p.el.style.transform = `translate(-50%, -50%) scale(${finalScale}) rotate(${rot}deg)`;
p.el.style.opacity = alpha;
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 {
allFinished = false;
}
}
const allDone = particles.every(p => p.isSpace || p.finished || p.fadingOut);
if (allDone) {
if (callback) callback();
return;
}
animFrameId = requestAnimationFrame(step);
}
animFrameId = requestAnimationFrame(step);
}
function fadeOutParticles(particles, duration, callback) {
const start = performance.now();
function step(now) {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
const ease = 1 - Math.pow(1 - progress, 2);
for (const p of particles) {
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) {
for (const p of particles) {
if (p.el.parentNode) p.el.parentNode.removeChild(p.el);
}
currentParticles = currentParticles.filter(p => !particles.includes(p));
if (callback) callback();
return;
}
requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
function showLyric(index, callback) {
if (isTransitioning) return;
if (index < 0 || index >= LYRICS.length) return;
isTransitioning = true;
const rect = container.getBoundingClientRect();
const cx = rect.width / 2, cy = rect.height / 2;
if (currentParticles.length > 0) {
const old = currentParticles.slice();
fadeOutParticles(old, PARTICLE_CONFIG.fadeOutDuration, () => {
createAndShow(index, cx, cy, callback);
});
} else {
createAndShow(index, cx, cy, callback);
}
}
function createAndShow(index, cx, cy, callback) {
const lyric = LYRICS;
const newParticles = createLyricParticles(lyric.text, cx, cy);
currentParticles = newParticles;
animateLyricParticles(newParticles, performance.now(), () => {
isTransitioning = false;
if (callback) callback();
});
currentIndex = index;
}
function startColorLoop() {
if (colorLoopId) cancelAnimationFrame(colorLoopId);
let startTime = performance.now();
function updateColors(timestamp) {
const elapsed = (timestamp - startTime) / 1000;
for (const p of currentParticles) {
if (p.isSpace) continue;
if (!p.el.parentNode) continue;
const hue = (elapsed * 30 + p.phase * 180 / Math.PI) % 360;
const color = `hsl(${hue}, 100%, 45%)`;
p.el.style.color = color;
p.el.style.textShadow = shadow;
}
colorLoopId = requestAnimationFrame(updateColors);
}
colorLoopId = requestAnimationFrame(updateColors);
}
startColorLoop();
const audio = document.getElementById('audio');
const centerBtn = document.getElementById('centerBtn');
const btnTimeRemain = document.getElementById('btnTimeRemain');
const progressCircle = document.querySelector('.progress-ring-circle');
const spectrumPlayer = document.getElementById('spectrumPlayer');
const fwCanvas = document.getElementById('fireworksCanvas');
const fwCtx = fwCanvas.getContext('2d');
const loading = document.getElementById('loading');
const totalDots = 24;
const innerRadius = 38;
const circumference = 2 * Math.PI * innerRadius;
let spectrumDots = [];
let spectrumAnimId = null;
let fireworks = [];
let fwWidth, fwHeight, fwCx, fwCy;
let globalRotation = 0;
const ROT_SPEED = 0.0068;
let originalAngles = [];
let dotRadius = 0;
let dotCenter = { x: 0, y: 0 };
let state = { isPlaying: false };
let lastDisplayIndex = -1;
let videoVisible = true;
function resizeFireworks() {
const rect = spectrumPlayer.getBoundingClientRect();
fwWidth = rect.width;
fwHeight = rect.height;
fwCanvas.width = fwWidth;
fwCanvas.height = fwHeight;
fwCx = fwWidth / 2;
fwCy = fwHeight / 2;
}
function updateDotGeo() {
const rect = spectrumPlayer.getBoundingClientRect();
dotCenter.x = rect.width / 2;
dotCenter.y = rect.height / 2;
dotRadius = Math.min(rect.width, rect.height) * 0.52;
}
function rebuildDots() {
const containerDots = document.getElementById('spectrumDots');
containerDots.innerHTML = '';
spectrumDots = [];
originalAngles = [];
updateDotGeo();
for (let i = 0; i < totalDots; i++) {
const angle = (i / totalDots) * 2 * Math.PI;
originalAngles.push(angle);
const dot = document.createElement('div');
dot.className = 'spectrum-dot';
const x = dotCenter.x + dotRadius * Math.cos(angle);
const y = dotCenter.y + dotRadius * Math.sin(angle);
dot.style.left = x + 'px';
dot.style.top = y + 'px';
containerDots.appendChild(dot);
spectrumDots.push(dot);
}
}
function updateDotsPos() {
for (let i = 0; i < spectrumDots.length; i++) {
const dot = spectrumDots;
const finalAngle = originalAngles + globalRotation;
const px = dotCenter.x + dotRadius * Math.cos(finalAngle);
const py = dotCenter.y + dotRadius * Math.sin(finalAngle);
dot.style.left = px + 'px';
dot.style.top = py + 'px';
}
}
class Firework {
constructor(angle, speed, color) {
this.x = fwCx;
this.y = fwCy;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.life = 1;
this.alpha = 1;
this.radius = Math.random() * 2 + 1;
this.color = color;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= 0.02;
this.alpha = this.life;
}
draw(ctx) {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
}
function emitFireworks(intensity) {
const count = Math.floor(intensity * 4);
const baseSpeed = 1.5 + intensity * 2;
const colors = ['#ff0000','#00ff00','#d4b8ff','#fff000'];
for (let i = 0; i < count; i++) {
const angle = Math.random() * 2 * Math.PI;
const speed = baseSpeed * (0.5 + Math.random() * 0.5);
const color = colors;
fireworks.push(new Firework(angle, speed, color));
}
}
function animateFireworks() {
fwCtx.clearRect(0, 0, fwWidth, fwHeight);
fireworks = fireworks.filter(p => p.life > 0);
fireworks.forEach(p => { p.update(); p.draw(fwCtx); });
if (state.isPlaying) requestAnimationFrame(animateFireworks);
else fwCtx.clearRect(0, 0, fwWidth, fwHeight);
}
function generateSpectrum() {
const t = Date.now() / 250;
const data = [];
let maxVal = 0;
for (let i = 0; i < totalDots; i++) {
let val = 0.4 + Math.sin(t + i * 0.3) * 0.4 + Math.random() * 0.3;
val = Math.min(1, Math.max(0.3, val));
data.push(val);
if (val > maxVal) maxVal = val;
}
return { data, intensity: maxVal };
}
function startSpectrumAnim() {
if (spectrumAnimId) cancelAnimationFrame(spectrumAnimId);
if (!state.isPlaying) return;
animateFireworks();
function loop() {
if (!state.isPlaying) return;
globalRotation += ROT_SPEED;
if (globalRotation > 2 * Math.PI) globalRotation -= 2 * Math.PI;
updateDotsPos();
const { data, intensity } = generateSpectrum();
if (intensity > 0.68) emitFireworks(intensity);
for (let i = 0; i < spectrumDots.length; i++) {
const dot = spectrumDots;
const val = data;
const scale = 0.7 + val * 0.9;
dot.style.transform = `scale(${scale})`;
const hue = 240 + val * 40;
dot.style.background = `hsl(${hue}, 95%, ${58 + val * 28}%)`;
const glow = 4 + val * 12;
dot.style.boxShadow = `0 0 ${glow}px hsl(${hue + 10}, 100%, 65%), 0 0 3px #b896ff`;
}
spectrumAnimId = requestAnimationFrame(loop);
}
loop();
}
function formatNeg(sec) {
if (isNaN(sec) || sec <= 0) return '-00:00';
const m = Math.floor(sec / 60), s = Math.floor(sec % 60);
return `-${m}:${s < 10 ? '0' : ''}${s}`;
}
function updateProgress() {
if (!audio.duration) return;
const progress = audio.currentTime / audio.duration;
progressCircle.style.strokeDashoffset = circumference * (1 - progress);
btnTimeRemain.textContent = formatNeg(audio.duration - audio.currentTime);
progressCircle.style.stroke = `hsl(${240 + progress * 40}, 100%, 55%)`;
}
function getAngleFromEvent(e, rect) {
const cx = rect.left + rect.width / 2, cy = rect.top + rect.height / 2;
let clientX = e.touches ? e.touches.clientX : e.clientX;
let clientY = e.touches ? e.touches.clientY : e.clientY;
let angle = Math.atan2(clientY - cy, clientX - cx);
if (angle < 0) angle += 2 * Math.PI;
let progressAngle = angle - Math.PI / 2;
if (progressAngle < 0) progressAngle += 2 * Math.PI;
return progressAngle / (2 * Math.PI);
}
function seekByEvent(e, rect) {
const progress = getAngleFromEvent(e, rect);
if (!audio.duration) return;
audio.currentTime = progress * audio.duration;
if (bgVideo && bgVideo.readyState >= 1 && videoVisible) bgVideo.currentTime = audio.currentTime;
updateProgress();
}
function bindCircularEvents() {
const p = spectrumPlayer;
let dragging = false;
const onMove = (e) => { if (!dragging) return; e.preventDefault(); seekByEvent(e, p.getBoundingClientRect()); };
const onUp = () => { dragging = false; document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
p.addEventListener('mousedown', (e) => {
if (e.target === centerBtn || centerBtn.contains(e.target)) return;
e.preventDefault();
dragging = true;
seekByEvent(e, p.getBoundingClientRect());
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
p.addEventListener('touchstart', (e) => {
if (e.target === centerBtn || centerBtn.contains(e.target)) return;
e.preventDefault();
dragging = true;
seekByEvent(e, p.getBoundingClientRect());
document.addEventListener('touchmove', onMove);
document.addEventListener('touchend', onUp);
});
p.addEventListener('click', (e) => {
if (e.target === centerBtn || centerBtn.contains(e.target)) return;
seekByEvent(e, p.getBoundingClientRect());
});
}
function syncLyric() {
if (audio.paused && audio.currentTime === 0) {
if (lastDisplayIndex !== 0 && LYRICS.length) {
lastDisplayIndex = 0;
showLyric(0);
}
return;
}
const ct = audio.currentTime;
let idx = 0;
for (let i = LYRICS.length - 1; i >= 0; i--) {
if (LYRICS.time <= ct) { idx = i; break; }
}
if (idx !== lastDisplayIndex) {
lastDisplayIndex = idx;
showLyric(idx);
}
}
function togglePlay() {
if (audio.paused) {
audio.play().catch(e => console.warn('播放失败', e));
if (videoVisible) {
bgVideo.play().catch(e => {});
}
} else {
audio.pause();
bgVideo.pause();
}
updateUI();
}
function updateUI() {
state.isPlaying = !audio.paused;
if (state.isPlaying) {
startSpectrumAnim();
updateProgress();
syncLyric();
} else {
if (spectrumAnimId) cancelAnimationFrame(spectrumAnimId);
spectrumAnimId = null;
}
}
centerBtn.onclick = () => audio.paused ? (reflect.unpauseAnimations()) : (reflect.pauseAnimations());
function initEvents() {
centerBtn.addEventListener('click', (e) => { e.stopPropagation(); togglePlay(); });
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') { e.preventDefault(); togglePlay(); }
});
audio.addEventListener('timeupdate', () => {
if (state.isPlaying) syncLyric();
updateProgress();
});
audio.addEventListener('play', updateUI);
audio.addEventListener('pause', updateUI);
audio.addEventListener('ended', () => { updateUI(); });
bindCircularEvents();
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
resizeFireworks();
updateDotGeo();
rebuildDots();
updateDotsPos();
if (currentParticles.length && !isTransitioning) {
const rect = container.getBoundingClientRect();
const cx = rect.width / 2, cy = rect.height / 2;
const text = LYRICS?.text || '';
if (text) {
for (const p of currentParticles) {
if (p.el.parentNode) p.el.parentNode.removeChild(p.el);
}
currentParticles = [];
const newP = createLyricParticles(text, cx, cy);
currentParticles = newP;
animateLyricParticles(newP, performance.now(), () => { isTransitioning = false; });
}
}
}, 300);
});
}
function init() {
resizeFireworks();
updateDotGeo();
rebuildDots();
if (LYRICS.length) {
lastDisplayIndex = 0;
setTimeout(() => {
const rect = container.getBoundingClientRect();
const cx = rect.width / 2, cy = rect.height / 2;
const text = LYRICS.text;
const newP = createLyricParticles(text, cx, cy);
currentParticles = newP;
animateLyricParticles(newP, performance.now(), () => { isTransitioning = false; });
}, 100);
}
initEvents();
loading.style.display = 'block';
audio.play().then(() => {
loading.style.display = 'none';
if (videoVisible) {
bgVideo.play().catch(() => {});
}
updateUI();
}).catch(() => {
loading.style.display = 'none';
const hint = document.createElement('div');
const startHandler = () => {
audio.play().then(() => {
if (videoVisible) {
bgVideo.play().catch(() => {});
}
hint.remove();
updateUI();
document.removeEventListener('click', startHandler);
}).catch(() => {});
};
hint.addEventListener('click', startHandler);
document.addEventListener('click', startHandler);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
</script> 歌词字幕这样弹出来,新颖有趣{:4_173:} 漂亮~谢谢亚伦老师精彩分享{:4_190:} 半圆形奇特歌词,歌词字体和颜色还是随机的,好奇特。
小播也很漂亮。欣赏亚伦老师好帖{:4_187:} 这样的歌词很奇特,但看字不是很方便呢{:4_173:} 歌词的字母设计独特。
页:
[1]