胡琴说
本帖最后由 杨帆 于 2026-7-15 21:40 编辑 <br /><br /><!DOCTYPE html><html lang="zh-CN">
<head>
<meta charset="UTF-8">
<link href="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" rel="stylesheet">
<title>胡琴说</title>
<style>
@import 'https://638183.freep.cn/638183/web/api/audioplayer.css';
.pa { position: relative; --offsetX: 81px; --state: running; --bg: url('https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/b5989127f299e693180bc3eb406a3d16.jpg') no-repeat center/cover; color: lightblue; width: 1280px; height: 750px; overflow: hidden; z-index: 1; }
.pa::before { position: absolute; content: ''; inset: 0; transition: .75s; mix-blend-mode: screen; background: url('https://s3.bmp.ovh/2026/07/15/Ns7ErMKV.png') no-repeat center/50% 50%; display: none; }
.pa:hover::before { transform: scale(1.05); display: block; }
.pa:fullscreen::before { transform: unset; }
.player { width: 480px; bottom: 10px; }
.btnFs { top: 25px; right: 20px; }
#vid { position: absolute; left: 0; top: -90px; width: 100%; height: calc(100% +90px); object-fit: cover; pointer-events: none; z-index: 1; mix-blend-mode: screen; }
.li-zi { position: absolute; width: 60px; height: 60px; background: url('https://upfile.mp3.wf/view.php/1c7e48bad4098a166234b5528d0c8740.png') no-repeat center/cover; offset-path: path('M0,200 Q390,-180 1800,200'); animation: move 10s linear infinite var(--state); }
@keyframes move { to { offset-distance: 100%; } }
#msvg { position: absolute; z-index: 15; left: 45px; top: 45px; width: 120px; height: 120px; display: block; }
#cpath { cursor: pointer; filter: drop-shadow(0 0 4px #000); transform-origin: 50%; animation: rot 6s linear infinite var(--state), chgcolor 12s linear infinite alternate var(--state); }
@keyframes rot { to { transform: rotate(360deg); stroke-dashoffset: -160; } }
@keyframes chgcolor { to { fill: rgba(100,55,30,.1); } }
#lyricBox { width:1000px; height:200px; position:absolute; bottom:20px; left:50%; transform:translateX(-50%); z-index: 10; pointer-events:none; }
#mainCanvas { width:1000px; height:140px; position:absolute; top:0; left:0; background:transparent; }
</style>
</head>
<body>
<div class="pa">
<div class="player"></div>
<div id="lyricBox">
<canvas id="mainCanvas" width="1000" height="140"></canvas>
</div>
<svg id="msvg" width="200" height="200"viewBox="0 0 200 200"><title id = "svgTip">暂停</title>
<defs>
<g id="cpath"fill="rgba(210,105,30,.15)" stroke="rgba(255,255,255,.8)" stroke-width="4" stroke-dasharray="4">
<path d='M100 120 C10 -30,190 -30,100 120'/>
</g>
</defs>
</svg>
<video id = "vid" src="https://img2.tukuppt.com/video_show/7165162/00/17/64/5ecb83e0e7301.mp4" autoplay loop preload="auto" playsinline muted></video>
</div>
<script>
let lyricData = [];
let charList = [];
let lyricAnimId = null;
let isPlaying = true;
let audioElement = null;
const mainCanvas = document.getElementById('mainCanvas');
const mainCtx = mainCanvas.getContext('2d');
const CANVAS_W = 1000;
const CANVAS_H = 140;
const FONT_SIZE = 52;
const GAP = 0.22;
const RED_COLOR = "#ff2222";
function parseLrc(str) {
const arr = str.trim().split(/\r?\n/);
const list = [];
const reg = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/;
for (let line of arr) {
const res = line.match(reg);
if (!res) continue;
const m = parseInt(res);
const s = parseInt(res);
const ms = parseInt(res);
const time = m * 60 + s + ms / 1000;
const text = line.replace(reg, '').trim();
list.push({ time, text });
}
list.sort((a, b) => a.time - b.time);
return list;
}
function splitWord(list) {
const wordArr = [];
for (let i = 0; i < list.length; i++) {
const item = list;
const text = item.text;
const nextTime = list ? list.time : item.time + 6;
const dur = nextTime - item.time;
const len = text.length;
for (let j = 0; j < len; j++) {
wordArr.push({
char: text,
lineIdx: i,
wordIdx: j,
lineStart: item.time,
lineDur: dur,
startRatio: j / len,
endRatio: (j + 1) / len
});
}
}
return wordArr;
}
function drawWord(ctx, char, x, y, size, rate) {
ctx.save();
ctx.font = `bold ${size}px "Long Cang","ZCOOL KuaiLe","Ma Shan Zheng","华文行楷","SimHei","Arial","sans-serif"`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.strokeStyle = "rgba(255,255,255,0.98)";
ctx.lineWidth = 3.5;
ctx.lineJoin = "round";
ctx.strokeText(char, x, y);
const maxRadius = size * 0.9;
const radius = rate * maxRadius;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
ctx.fillStyle = RED_COLOR;
ctx.fillText(char, x, y);
ctx.restore();
}
function getTextPos(text, size, centerY) {
const wordGap = size * 1.05;
const totalW = text.length * wordGap;
const startX = CANVAS_W / 2 - totalW / 2 + wordGap / 2;
const posList = [];
for (let i = 0; i < text.length; i++) {
posList.push({
char: text,
x: startX + i * wordGap,
y: centerY,
idx: i
});
}
return posList;
}
function getNowLine(time, lrc) {
let idx = 0;
for (let i = 0; i < lrc.length; i++) {
if (time >= lrc.time) idx = i;
}
return idx;
}
function renderLyric(currentTime, lrc, wordArr) {
mainCtx.clearRect(0, 0, CANVAS_W, CANVAS_H);
const lineIdx = getNowLine(currentTime, lrc);
if (lineIdx >= lrc.length) return;
const lineInfo = lrc;
const nowWord = wordArr.filter(w => w.lineIdx === lineIdx);
const lineText = lineInfo.text;
const passTime = currentTime - lineInfo.time;
const lineRate = Math.max(0, Math.min(1, passTime / nowWord.lineDur));
let speed = 0.75;
if (nowWord.lineDur < 2) speed = 0.85;
if (nowWord.lineDur > 5) speed = 0.65;
let fontSize = FONT_SIZE;
if (lineText.length > 14) fontSize = FONT_SIZE * 0.88;
if (lineText.length > 10) fontSize = FONT_SIZE * 0.98;
const y = CANVAS_H / 2;
const posList = getTextPos(lineText, fontSize, y);
const wordTotal = posList.length;
for (let pos of posList) {
const wData = nowWord;
if (!wData) continue;
let r = 0;
const sR = wData.startRatio - GAP;
const eR = wData.endRatio + GAP;
if (lineRate >= sR && lineRate <= eR) {
const subRate = (lineRate - wData.startRatio) / (wData.endRatio - wData.startRatio);
r = Math.max(0.08, Math.min(1, subRate * speed));
} else if (lineRate > wData.endRatio) {
r = 1;
} else if (lineRate > 0.5 && pos.idx / wordTotal < lineRate) {
r = 1;
}
const scale = 1 - 0.1 * Math.abs(pos.idx - wordTotal / 2) / wordTotal;
drawWord(mainCtx, pos.char, pos.x, pos.y, fontSize * scale, r);
}
}
function lyricLoop() {
if (!isPlaying || !audioElement) return;
renderLyric(audioElement.currentTime, lyricData, charList);
lyricAnimId = requestAnimationFrame(lyricLoop);
}
function pauseAnimation() { isPlaying = false; }
function resumeAnimation() { isPlaying = true; }
const gc = `胡琴说
演唱:王莉/汤非
词:张名河
曲:孟庆云
男:胡琴对你说
爱是一条河
花开花落岁月长
从你指尖流过
听水水有声
听山山有色
风来松涛鸣
雨去竹泪落
女:胡琴对我说
爱是一条河
花开花落岁月长
从我指尖流过
听水水有声
听山山有色
风来松涛鸣
雨去竹泪落
男:只因心中有爱
心中有爱
酸甜苦辣算什么
算什么
女:只要心中有爱
心中有爱
喜怒哀乐都是歌
都是歌
合:胡琴对你(我)说
爱是一条河
花开花落岁月长
从你(我)指尖流过
听水水有声
听山山有色
风来松涛鸣
雨去竹泪落
男:只因心中有爱
心中有爱
酸甜苦辣算什么
算什么
女:只要心中有爱
心中有爱
喜怒哀乐都是歌
都是歌
合:都是歌啊
合:都是歌啊
`;
const options = {
pa: '.pa',
urls: [
['https://music.163.com/song/media/outer/url?id=29023393', '胡琴说']
]
};
const pa = document.querySelector('.pa');
const vid = document.getElementById('vid');
function loadJs(url, callback) {
const script = document.createElement('script');
script.charset = 'utf-8';
script.src = url;
script.onload = function () {
if (callback) callback();
};
document.head.appendChild(script);
}
loadJs('https://638183.freep.cn/638183/web/api/audioplayer.min.js', tzRun);
['contextmenu', 'dragstart', 'selectstart'].forEach(function (ev) {
pa.addEventListener(ev, function (e) {
e.preventDefault();
});
});
function tzRun() {
const aud = new AudPlayer(options);
const audio = aud.aud;
audioElement = audio;
const msvg = document.getElementById('msvg');
const tip = document.getElementById('svgTip');
lyricData = parseLrc(gc);
charList = splitWord(lyricData);
lyricAnimId = requestAnimationFrame(lyricLoop);
msvg.onclick = function () {
if (audio.paused) {
audio.play().catch(() => {});
vid.play();
resumeAnimation();
lyricAnimId = requestAnimationFrame(lyricLoop);
} else {
audio.pause();
vid.pause();
pauseAnimation();
lyricAnimId = null;
}
tip.textContent = audio.paused ? '播放' : '暂停';
pa.style.setProperty('--state', audio.paused ? 'paused' : 'running');
};
}
mkLeaves = (total) => {
let str = '';
Array(total).fill().forEach((_,idx) => {
str += `<use href="#cpath" transform="rotate(${360 / total * idx} 100 100)"/>`;
});
return str;
};
msvg.innerHTML += mkLeaves(5);
const papa=document.querySelector('.pa')
function clearAllParticles() {
papa.querySelectorAll('.li-zi').forEach(item => item.remove());
}
Array.from({ length: 20 }).forEach(() => {
const size = 30 + Math.floor(Math.random() * 30);
const star = document.createElement('div');
star.className = 'li-zi';
star.style.cssText += `left: 0; top: ${Math.random() * 140 + 60}px; width: ${size}px; height: ${size}px; opacity: ${Math.random() * 0.4 + 0.4}; animation-delay: -${Math.random() * 10}s;`;
papa.prepend(star);
});
document.addEventListener('visibilitychange',()=>{
if(document.hidden){
if(!audioElement.paused)audioElement.pause();
}else{
if(audioElement.paused&&!audioElement.ended)audioElement.play().catch(()=>{});
}
});
</script>
</body>
</html> 代码来自马老师分享作品,在此表示感谢{:4_190:} 杨帆的音画是多元素的,真棒。歌词特效漂亮,欣赏点赞!{:4_187:} 梦江南 发表于 2026-7-15 20:44
杨帆的音画是多元素的,真棒。歌词特效漂亮,欣赏点赞!
谢谢江南鼓励~歌词特效代码来自小辣椒分享作品,在此一并表示感谢{:4_204:} 杨帆速度的,学习东西很快{:4_199:} 欣赏杨帆精彩的制作{:4_187:} 小辣椒 发表于 2026-7-15 22:03
杨帆速度的,学习东西很快
鼓捣着玩呢,谢谢小辣椒鼓励,祝夏安{:4_204:} 杨帆 发表于 2026-7-15 22:17
鼓捣着玩呢,谢谢小辣椒鼓励,祝夏安
歌词效果也是学习的快 打开帖子,小花飘舞。闻花香好,歌悦耳似,点赞佳作!祝扬帆制作愉快,晚上好~~{:4_204:}{:4_190:} 小辣椒 发表于 2026-7-15 22:21
歌词效果也是学习的快
主要你的歌词效果好,欣赏学习来劲呢{:4_187:} 霜染枫丹 发表于 2026-7-15 22:25
打开帖子,小花飘舞。闻花香好,歌悦耳似,点赞佳作!祝扬帆制作愉快,晚上好~~
谢谢枫丹老师雅评与鼓励,祝夏安{:4_204:} 歌词同步漂亮,小播也很有特色。非常漂亮的制作。给杨帆点赞{:4_187:}
那个随鼠标而出现的胡琴好像变形了呢{:4_173:} 谢谢杨帆老师精彩分享! 红影 发表于 2026-7-15 23:23
歌词同步漂亮,小播也很有特色。非常漂亮的制作。给杨帆点赞
那个随鼠标而出现的胡琴好像变形了 ...
没变形呀,等比缩放,谢谢影子鼓励{:4_204:} 雨季工作室 发表于 2026-7-16 08:28
谢谢杨帆老师精彩分享!
谢谢雨季鼓励,祝夏安{:4_190:} 杨帆 发表于 2026-7-16 21:40
没变形呀,等比缩放,谢谢影子鼓励
总觉得那胡琴有点怪呢{:4_173:} 红影 发表于 2026-7-17 20:45
总觉得那胡琴有点怪呢
是,绘图的夸张手法呗 杨帆 发表于 2026-7-18 09:05
是,绘图的夸张手法呗
哦哦,有可能的。
页:
[1]