杨帆 发表于 2026-7-15 20:01

胡琴说

本帖最后由 杨帆 于 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>

杨帆 发表于 2026-7-15 20:02

代码来自马老师分享作品,在此表示感谢{:4_190:}

梦江南 发表于 2026-7-15 20:44

杨帆的音画是多元素的,真棒。歌词特效漂亮,欣赏点赞!{:4_187:}

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

梦江南 发表于 2026-7-15 20:44
杨帆的音画是多元素的,真棒。歌词特效漂亮,欣赏点赞!

谢谢江南鼓励~歌词特效代码来自小辣椒分享作品,在此一并表示感谢{:4_204:}

小辣椒 发表于 2026-7-15 22:03

杨帆速度的,学习东西很快{:4_199:}

小辣椒 发表于 2026-7-15 22:03

欣赏杨帆精彩的制作{:4_187:}

杨帆 发表于 2026-7-15 22:17

小辣椒 发表于 2026-7-15 22:03
杨帆速度的,学习东西很快

鼓捣着玩呢,谢谢小辣椒鼓励,祝夏安{:4_204:}

小辣椒 发表于 2026-7-15 22:21

杨帆 发表于 2026-7-15 22:17
鼓捣着玩呢,谢谢小辣椒鼓励,祝夏安

歌词效果也是学习的快

霜染枫丹 发表于 2026-7-15 22:25

打开帖子,小花飘舞。闻花香好,歌悦耳似,点赞佳作!祝扬帆制作愉快,晚上好~~{:4_204:}{:4_190:}

杨帆 发表于 2026-7-15 22:49

小辣椒 发表于 2026-7-15 22:21
歌词效果也是学习的快

主要你的歌词效果好,欣赏学习来劲呢{:4_187:}

杨帆 发表于 2026-7-15 22:51

霜染枫丹 发表于 2026-7-15 22:25
打开帖子,小花飘舞。闻花香好,歌悦耳似,点赞佳作!祝扬帆制作愉快,晚上好~~

谢谢枫丹老师雅评与鼓励,祝夏安{:4_204:}

红影 发表于 2026-7-15 23:23

歌词同步漂亮,小播也很有特色。非常漂亮的制作。给杨帆点赞{:4_187:}
那个随鼠标而出现的胡琴好像变形了呢{:4_173:}

雨季工作室 发表于 2026-7-16 08:28

谢谢杨帆老师精彩分享!

杨帆 发表于 2026-7-16 21:40

红影 发表于 2026-7-15 23:23
歌词同步漂亮,小播也很有特色。非常漂亮的制作。给杨帆点赞
那个随鼠标而出现的胡琴好像变形了 ...

没变形呀,等比缩放,谢谢影子鼓励{:4_204:}

杨帆 发表于 2026-7-16 21:41

雨季工作室 发表于 2026-7-16 08:28
谢谢杨帆老师精彩分享!

谢谢雨季鼓励,祝夏安{:4_190:}

红影 发表于 2026-7-17 20:45

杨帆 发表于 2026-7-16 21:40
没变形呀,等比缩放,谢谢影子鼓励

总觉得那胡琴有点怪呢{:4_173:}

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

红影 发表于 2026-7-17 20:45
总觉得那胡琴有点怪呢

是,绘图的夸张手法呗

红影 发表于 2026-7-21 22:39

杨帆 发表于 2026-7-18 09:05
是,绘图的夸张手法呗

哦哦,有可能的。
页: [1]
查看完整版本: 胡琴说