频谱播放器-情深缘浅-毛惠
本帖最后由 亚伦影音工作室 于 2026-3-5 11:57 编辑 <br /><br /><style> @import url("https://fonts.googleapis.com/css2?family=Ma+Shan+Zheng&display=swap");#bj {
position: relative;
width: 1286px;
height: 720px;
margin-left: -300px;
margin-top: 0;
overflow: hidden;z-index:12345;
background:url(https://pic1.imgdb.cn/item/67162d12d29ded1a8c2f45cf.jpg)no-repeat center/cover;
--state: running;font-family: "Ma Shan Zheng","仿宋体","SimHei", "Arial", "sans-serif";
}
canvas {animation: flash 2s linear infinite ;
position: absolute;
width: 100%;
height: 100%;z-index: 7;
display: block;
cursor: pointer; pointer-events: none;
transition: transform 0.3s ease;
margin-top: -80px;margin-left: -120px;
}
canvas:hover {
transform: scale(1.05);
}
@keyframes flash {
to { filter: hue-rotate(360deg)brightness(260%); }
}
.lyric-preview { display: none;
position: absolute;
top: 240px;
left: 35%;
transform: translateX(-50%);
color: #ff0000;
font-size: 45px;
text-align: center;
width: 80%;
z-index: 7;
cursor: pointer; /**/
padding: 10px;
border-radius: 0px;
transition: background-color 0.2s ease;
}
.lyric-preview:hover {
color: #fff000;
}
.prev-lyric {
opacity: 0.8;color: #fff000;
margin-bottom: 8px;
transition: opacity 0.2s ease;
}
.current-lyric {
font-size: 55px;
font-weight: 300;
color: #ff0000
transition: opacity 0.2s ease;
}
.prev-lyric:hover {
transform: scale(1.15);
}
.next-lyric {
opacity: 0.8;color: #fff000;
margin-top: 8px;
transition: opacity 0.2s ease;
}
.lyric-preview:active .current-lyric {
opacity: 0.9;
}
#mdiv {top:12%; left:85%;cursor: pointer;width:120px;height: 120px;animation:rot 10s linear infinite var(--state);position: absolute;filter:drop-shadow(#000 0px 0 1px);z-index: 40;}
@keyframes rot { to { transform: rotate(2turn);} }
#toggleButton{position: absolute;margin-top: 30px; margin-left: 1050px;color:#ff0000; width:180px; height:80px; font-size:25px;cursor:pointer; text-align: center;z-index:1235;}
#toggleButton:hover { color:#fff;}
#canv {display: block; position: absolute;width:1300px; height:250px; bottom: 0px; left: 100px;z-index:6; animation: sp 60s linear infinite;}
@keyframes sp {
0% { filter:hue-rotate(360deg)contrast(250%)brightness(120%); }
}
</style>
<div id="bj">
<div class="lyric-preview" id="lyricPreview">
<div class="prev-lyric" id="prevLyric"></div>
<div class="current-lyric" id="currentLyric"></div>
<div class="next-lyric" id="nextLyric"></div>
</div>
<img id="mdiv"src="https://pic1.imgdb.cn/item/690c41ba3203f7be00db7fed.png">
<div id="toggleButton">多行歌词</div>
<canvas id="glcanvas" width="900" height="420"></canvas>
<canvas id='canv'width="1300" height="250"></canvas>
<audio id="audio" src="https://s2.cldisk.com/sv-w8/audio/42/10/40/e945979f7093df0932eb88d3d186a251/audio.mp3" loop autoplay crossOrigin="anonymous"></audio>
</div>
<script>
const state = {
currentLyricIndex: 0,
isAudioEnabled: true,
isPlaying: false,
mouseX: 0,
mouseY: 0,
isHovering: false,
lyrics: [],
hoverValue: 0,
hoverSpeed: 0.05, // 降低hover动画速度,更流畅
lastMouseMoveTime: 0,
throttleDelay: 100 // 鼠标事件节流延迟
};
const elements = {
canvas: document.getElementById('glcanvas'),
audio: document.getElementById('audio'),
lyricPreview: document.getElementById('lyricPreview'), // 歌词容器
prevLyric: document.getElementById('prevLyric'),
currentLyric: document.getElementById('currentLyric'),
nextLyric: document.getElementById('nextLyric')
};
// ===================== 初始化检测 =====================
function initCheck() {
// WebGL兼容性检测
const gl = elements.canvas.getContext('webgl') || elements.canvas.getContext('experimental-webgl');
if (!gl) {
state.isAudioEnabled = false; //
return null;
}
return gl;
}
// ===================== LRC歌词解析 =====================
function parsePureLRC(lrcText) {
const lines = [];
const lrcLines = lrcText.split('\n')
.map(line => line.trim())
.filter(line => line && !line.includes('●') && !line.includes('谢谢欣赏'));
for (const line of lrcLines) {
const timeRegex = /\[(\d{2}):(\d{2})(?:\.(\d{1,3}))?\]/g;
const text = line.replace(timeRegex, '').trim();
let timeMatch;
while ((timeMatch = timeRegex.exec(line)) !== null) {
const minutes = parseInt(timeMatch);
const seconds = parseInt(timeMatch);
const ms = timeMatch ? parseInt(timeMatch.padEnd(3, '0')) : 0;
const time = minutes * 60 + seconds + ms / 1000;
if (text) {
lines.push({ time, text });
}
}
}
return lines.sort((a, b) => a.time - b.time);
}
// ===================== 歌词跳转 =====================
function jumpToLyricByText(clickText) {
if (!clickText.trim()) return;
const targetLyric = state.lyrics.find(lyric => lyric.text === clickText.trim());
if (!targetLyric) return;
elements.audio.currentTime = targetLyric.time;
const targetIndex = state.lyrics.findIndex(lyric => lyric.text === clickText.trim());
if (targetIndex !== -1) {
state.currentLyricIndex = targetIndex;
elements.prevLyric.textContent = state.lyrics?.text || '';
elements.currentLyric.textContent = state.lyrics.text;
elements.nextLyric.textContent = state.lyrics?.text || '';
if (window.render && window.render.updateTextTexture) {
window.render.updateTextTexture(state.lyrics.text);
}
}
}
// ===================== WebGL渲染 =====================
function initWebGL(gl) {
function compileShader(src, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, src);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('着色器错误:', gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
const vertexShaderSource = `
attribute vec2 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos, 0.0, 1.0);
}
`;
const fragmentShaderSource = `
precision highp float;
varying vec2 v_uv;
uniform sampler2D u_text;
uniform float u_time;
uniform vec2 u_mouse;
uniform float u_hover;
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0,0.0));
float c = random(i + vec2(0.0,1.0));
float d = random(i + vec2(1.0,1.0));
vec2 u = f*f*(3.0-2.0*f);
return mix(a,b,u.x) + (c-a)*u.y*(1.0-u.x) + (d-b)*u.x*u.y;
}
void main() {
vec2 correctedUV = vec2(v_uv.x, 1.0-v_uv.y);
float strength = smoothstep(0.2,1.0,correctedUV.y);
float noiseTime = u_time * 0.3;
float n = noise(vec2(correctedUV.x*5.0, (correctedUV.y*2.0 - noiseTime)*2.0));
float melt = n * 0.4 * (correctedUV.y);
float dist = distance(correctedUV, u_mouse);
float hoverWave = 0.0;
if (u_hover > 0.1) {
hoverWave = sin(dist*12.0 - u_time*6.0)*0.08*u_hover*exp(-dist*3.5) +
sin(dist*20.0 - u_time*10.0)*0.04*u_hover*exp(-dist*5.0);
}
vec2 hoverDistort = normalize(correctedUV - u_mouse) * hoverWave * (1.0 + sin(u_time*2.0)*0.1);
vec2 finalUV = vec2(
correctedUV.x + melt*0.3 + hoverDistort.x,
correctedUV.y - melt*0.8 + hoverDistort.y
);
vec4 col = texture2D(u_text, finalUV);
if (col.a > 0.1) {
float glow = smoothstep(0.5,1.0,col.a);
vec3 spectrumColor = vec3(correctedUV.y, abs(sin(u_time + correctedUV.x*5.0)), 1.0-correctedUV.y);
if (u_hover > 0.1) {
float hoverIntensity = 1.0 - smoothstep(0.0,0.3,dist);
float pulseEffect = 0.7 + 0.3*sin(u_time*4.0 + dist*8.0);
vec3 goldColor = vec3(0.9, 0.6+0.3*pulseEffect, 0.2+0.2*sin(u_time*3.0));
vec3 blueColor = vec3(0.3+0.4*sin(u_time*2.0 + dist*6.0), 0.6+0.3*pulseEffect, 0.9);
spectrumColor = mix(spectrumColor, mix(goldColor, blueColor, 0.5), hoverIntensity*u_hover*0.8);
glow += hoverIntensity*u_hover*0.8;
}
col.rgb = mix(col.rgb, spectrumColor, 0.6*glow);
// 优化粒子效果
if (u_hover > 0.1 && dist < 0.2) {
float particles = noise(correctedUV*15.0 + u_time*2.0);
if (particles > 0.85) col.rgb += vec3(1.0,0.8,0.4)*(particles-0.85)*8.0*u_hover;
float sparkles = noise(correctedUV*30.0 + u_time*4.0);
if (sparkles > 0.92 && dist < 0.15) col.rgb += vec3(0.9,0.9,1.0)*(sparkles-0.92)*6.0*u_hover;
}
}
if (u_hover > 0.1) {
float ambientGlow = 1.0 - smoothstep(0.0,0.4,dist);
float auraEffect = sin(dist*6.0 - u_time*3.0)*0.2 + 0.8;
vec3 auraColor = vec3(
0.15+0.1*sin(u_time*1.5),
0.1+0.1*sin(u_time*2.0+1.0),
0.2+0.15*sin(u_time*1.2+2.0)
);
col.rgb += auraColor*ambientGlow*u_hover*auraEffect;
float outerHalo = 1.0 - smoothstep(0.2,0.5,dist);
col.rgb += vec3(0.08,0.04,0.12)*outerHalo*u_hover*0.3;
}
gl_FragColor = col;
}
`;
const vs = compileShader(vertexShaderSource, gl.VERTEX_SHADER);
const fs = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
const quad = new Float32Array([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, 1, 1, 1
]);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
const a_pos = gl.getAttribLocation(program, 'a_pos');
const a_uv = gl.getAttribLocation(program, 'a_uv');
gl.enableVertexAttribArray(a_pos);
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 16, 0);
gl.enableVertexAttribArray(a_uv);
gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, false, 16, 8);
const uniforms = {
u_time: gl.getUniformLocation(program, 'u_time'),
u_text: gl.getUniformLocation(program, 'u_text'),
u_mouse: gl.getUniformLocation(program, 'u_mouse'),
u_hover: gl.getUniformLocation(program, 'u_hover')
};
const textCanvas = document.createElement('canvas');
const tctx = textCanvas.getContext('2d');
textCanvas.width = elements.canvas.width;
textCanvas.height = elements.canvas.height;
const textTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, textTex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
function updateTextTexture(text) {
tctx.clearRect(0, 0, textCanvas.width, textCanvas.height);
tctx.fillStyle = '#000';
tctx.font = '600 42px "Ma Shan Zheng","仿宋体","SimHei", "Arial"';
tctx.textAlign = 'center';
tctx.textBaseline = 'middle';
tctx.fillText(text || '', textCanvas.width / 2, textCanvas.height / 2);
gl.bindTexture(gl.TEXTURE_2D, textTex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textCanvas);
}
// 绘制函数
function draw(t) {
if (!state.isAudioEnabled) return;
if (state.isHovering && state.hoverValue < 1.0) {
state.hoverValue = Math.min(1.0, state.hoverValue + state.hoverSpeed);
} else if (!state.isHovering && state.hoverValue > 0.0) {
state.hoverValue = Math.max(0.0, state.hoverValue - state.hoverSpeed);
}
gl.viewport(0, 0, elements.canvas.width, elements.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
const time = t * 0.001;
gl.uniform1f(uniforms.u_time, time);
gl.uniform2f(uniforms.u_mouse, state.mouseX, state.mouseY);
gl.uniform1f(uniforms.u_hover, state.hoverValue);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textTex);
gl.uniform1i(uniforms.u_text, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(draw);
}
return { updateTextTexture, draw };
}
// ===================== 歌词同步 =====================
function updateLyric() {
if (!state.isAudioEnabled || !state.lyrics.length ) return;
const currentTime = elements.audio.currentTime;
const { lyrics } = state;
for (let i = 0; i < lyrics.length; i++) {
const currentLyricTime = lyrics.time;
const nextLyricTime = i < lyrics.length - 1 ? lyrics.time : Infinity;
if (currentTime >= currentLyricTime && currentTime < nextLyricTime && state.currentLyricIndex !== i) {
state.currentLyricIndex = i;
if (window.render && window.render.updateTextTexture) {
window.render.updateTextTexture(lyrics.text);
}
// 更新歌词预览
elements.prevLyric.textContent = lyrics?.text || '';
elements.currentLyric.textContent = lyrics.text;
elements.nextLyric.textContent = lyrics?.text || '';
break;
}
}
requestAnimationFrame(updateLyric);
}
// ===================== 事件绑定 =====================
function bindEvents(gl) {
window.addEventListener('resize', () => {
if (!gl) return;
const width = elements.canvas.parentElement.clientWidth * 0.95;
const height = elements.canvas.parentElement.clientHeight * 0.6;
elements.canvas.width = width;
elements.canvas.height = height;
});
elements.canvas.addEventListener('mousemove', (e) => {
const now = Date.now();
if (now - state.lastMouseMoveTime < state.throttleDelay) return;
state.lastMouseMoveTime = now;
if (state.isAudioEnabled) {
const rect = elements.canvas.getBoundingClientRect();
state.mouseX = (e.clientX - rect.left) / rect.width;
state.mouseY = 1.0 - (e.clientY - rect.top) / rect.height;
state.isHovering = true;
}
});
elements.canvas.addEventListener('mouseleave', () => {
state.isHovering = false;
});
elements.lyricPreview.addEventListener('click', (e) => {
const target = e.target;
if (target.classList.contains('prev-lyric') || target.classList.contains('current-lyric') || target.classList.contains('next-lyric')) {
const clickText = target.textContent;
jumpToLyricByText(clickText);
}
});
}
// ==========================================
async function init() {
// LRC
const pureLRCText = `
情深缘浅-毛惠
作词:梦云
作曲:梦云
编曲:光大音乐
制作:亚伦影音工作室
混音:阿KEN
监制:梦云
制作人:亚伦
出品人:亚伦
演唱:毛惠
你曾说我是你命中的红颜
你会相厮相守爱我一百年
谱就风来尘往不弃的誓言
让我拥有每个幸福的明天
我一直陶醉在相爱的港湾
相信爱的世界里没有谎言
从没想过你会这样来敷衍
为何爱到最后却成了分飞的燕
是什么 让我们不再相恋
是什么 让我们走到终点
如果说今生和你情深缘浅
浪漫红尘为何会把你遇见
是什么 让我们不再相恋
是什么 让你放弃了誓言
如果说相爱只是我一厢情愿
就让我忘了 放了 不让你为难
情深缘浅-毛惠(原唱)
作词:梦云
作曲:梦云
编曲:光大音乐
制作:亚伦影音工作室
混音:阿KEN
监制:梦云
制作人:亚伦
出品人:亚伦
演唱:毛惠
我一直陶醉在相爱的港湾
相信爱的世界里没有谎言
从没想过你会这样来敷衍
为何爱到最后却成了分飞的燕
是什么 让我们不再相恋
是什么 让我们走到终点
如果说今生和你情深缘浅
浪漫红尘为何会把你遇见
是什么 让我们不再相恋
是什么 让你放弃了誓言
如果说相爱只是我一厢情愿
就让我忘了 放了 不让你为难
如果说相爱只是我一厢情愿
就让我忘了 放了 不让你为难
未经应许,侵权必究!
`;
state.lyrics = parsePureLRC(pureLRCText);
const gl = initCheck();
if (!gl) return;
window.render = initWebGL(gl);
bindEvents(gl);
requestAnimationFrame(window.render.draw);
updateLyric();
window.render.updateTextTexture(state.lyrics?.text || '');
elements.prevLyric.textContent = '';
elements.currentLyric.textContent = state.lyrics?.text || '';
elements.nextLyric.textContent = state.lyrics?.text || '';
}
window.addEventListener('load', init);
mdiv.onclick = () => audio.paused ?audio.play(): audio.pause();
mState = () => {bj.style.setProperty('--state', audio.paused ?'paused' : 'running');
};
audio.onplaying = audio.onpause = () => mState();;
let fss = true;toggleButton.innerText = '多行歌词';
toggleButton.onclick = () => {
if (fss) {toggleButton.innerText = '多行歌词';
lyricPreview.style.display = 'none';
glcanvas.style.display = 'block';
}else {toggleButton.innerText = '特效歌词';
lyricPreview.style.display = 'block';
glcanvas.style.display = 'none';
}
fss = !fss;
};
</script>
<script>
(function () {
let Act = new AudioContext();
let audSrc = Act.createMediaElementSource(audio);
let analyser = Act.createAnalyser();
audSrc.connect(analyser);
analyser.connect(Act.destination);
let ctx = canv.getContext('2d');
let width = canv.width;
let height = canv.height;
let ppColor = ctx.createLinearGradient(250,200,250,0);
ppColor.addColorStop(0.98, '#ffaa00');
let ppNum = 1300;
let voiceHeight = new Uint8Array(analyser.frequencyBinCount);
(function draw() {
analyser.getByteFrequencyData(voiceHeight);
let step = Math.round(voiceHeight.length / ppNum);
ctx.clearRect(0, 0, width, height);
for (let j = 0; j < ppNum; j++) {
let audiheighteight = voiceHeight;
ctx.fillStyle = ppColor;
ctx.fillRect(width / 2+ (j * 4), height, 3, -audiheighteight);
ctx.fillRect(width / 2- (j * 4), height, 3, -audiheighteight);
}
window.requestAnimationFrame(draw);
})();
})();
</script> 频谱好漂亮,谢谢精彩分享,祝亚伦老师元宵节快乐{:4_176:} 漂亮的变色频谱,还有特效歌词和多行歌词的切换。
这个制作真漂亮,欣赏亚伦老师好帖{:4_187:} 哈哈,亚纶特别喜欢频谱,欣赏亚纶漂亮的频谱播放器{:4_178:} 好漂亮的频谱,欣赏学习了。
欣赏佳作,问候亚伦。
页:
[1]