{"id":3061,"date":"2025-06-09T15:56:55","date_gmt":"2025-06-09T07:56:55","guid":{"rendered":"https:\/\/blog.kangyue.pro\/?p=3061"},"modified":"2025-06-09T19:31:44","modified_gmt":"2025-06-09T11:31:44","slug":"%e7%94%b1-chatgpt-%e7%94%9f%e6%88%90%e7%9a%84%e9%9f%b3%e4%b9%90%e5%8f%af%e8%a7%86%e5%8c%96html","status":"publish","type":"post","link":"https:\/\/blog.kangyue.pro\/?p=3061","title":{"rendered":"\u7531 ChatGPT \u53ca Gemini \u751f\u6210\u7684\u7eaf HTML \u97f3\u4e50\u53ef\u89c6\u5316\u64ad\u653e\u5668"},"content":{"rendered":"\n<div class=\"wp-block-gutena-testimonials gutena-testimonial-block gutena-testimonial-block-43196b-1c align-left has-box-shadow has-nav-dot has-nav-arrow\"><div class='gutena-testimonial-slider' data-slider-settings='{\"autoplay\":true,\"autoplayTimeout\":5000,\"autoplayDirection\":\"forward\",\"speed\":500,\"responsive\":{\"300\":{\"nav\":true,\"controls\":true,\"items\":1,\"gutter\":5},\"640\":{\"nav\":true,\"controls\":true,\"items\":1,\"gutter\":15},\"1024\":{\"nav\":true,\"controls\":true,\"items\":1,\"gutter\":20}}}'>\n<div class=\"gutena-testimonial-slide\"><div class=\"wp-block-gutena-testimonial-item gutena-testimonial-item-block\">\n<div class=\"wp-block-gutena-testimonial-icon gutena-testimonial-icon-block\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M4.583 17.321C3.553 16.227 3 15 3 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311 1.804.167 3.226 1.648 3.226 3.489a3.5 3.5 0 0 1-3.5 3.5c-1.073 0-2.099-.49-2.748-1.179zm10 0C13.553 16.227 13 15 13 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311 1.804.167 3.226 1.648 3.226 3.489a3.5 3.5 0 0 1-3.5 3.5c-1.073 0-2.099-.49-2.748-1.179z\"><\/path><\/svg><\/div>\n\n\n\n<div class=\"wp-block-gutena-testimonial-text gutena-testimonial-text-block\"><div class=\"gutena-testimonial-text-content\">\u8be5\u300cWeb\u7248\u97f3\u4e50\u53ef\u89c6\u5316\u64ad\u653e\u5668\u300d\uff0c\u57fa\u7840\u6846\u67b6\u7531ChatGPT\u751f\u6210\uff0c\u914d\u5408Google Gemini\u7684\u6539\u8fdb\u800c\u6210\u3002\u524d\u540e\u4fee\u6539\u6570\u5341\u6b21\uff0c\u7ec8\u6210\u672c\u7248\u3002<\/div><\/div>\n<\/div><\/div>\n<\/div><\/div>\n\n\n\n<h1 class=\"wp-block-heading\">Demo<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/kangyue.pro\/html\/VisualMusic\/\">https:\/\/kangyue.pro\/html\/VisualMusic\/<\/a><\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">\u622a\u56fe<\/h1>\n\n\n\n<p>PC\u7248\u5e03\u5c40\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1706\" height=\"863\" src=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-77.png\" alt=\"\" class=\"wp-image-3073\" srcset=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-77.png 1706w, https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-77-768x389.png 768w, https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-77-1536x777.png 1536w\" sizes=\"auto, (max-width: 1706px) 100vw, 1706px\" \/><\/figure>\n\n\n\n<p>\u624b\u673a\u7248\u5e03\u5c40\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"863\" src=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-78.png\" alt=\"\" class=\"wp-image-3074\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">\u529f\u80fd\u4ecb\u7ecd<\/h1>\n\n\n\n<h3 class=\"wp-block-heading\">\u97f3\u9891\u53ef\u89c6\u5316<\/h3>\n\n\n\n<p>\u6839\u636e\u97f3\u9891\u7684\u6ce2\u52a8\u5c55\u793a\u76f8\u5e94\u7684\u52a8\u6001\u753b\u9762\uff0c\u5305\u62ec\u56db\u79cd\u6548\u679c\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u6761\u5f62\u56fe<\/li>\n\n\n\n<li>\u6ce2\u6d6a\u56fe<\/li>\n\n\n\n<li>\u7ec4\u4e50\u5708<\/li>\n\n\n\n<li>\u7c92\u5b50\u6ce1\u6ce1<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u6b4c\u8bcd<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5c55\u793a\u5b8c\u6574\u7684\u6b4c\u8bcd<\/li>\n\n\n\n<li>\u5f53\u524d\u64ad\u653e\u7684\u884c<mark style=\"background-color:rgba(0, 0, 0, 0);color:#27debf\" class=\"has-inline-color\"><strong>\u9ad8\u4eae<\/strong><\/mark>\u663e\u793a<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u500d\u901f\u64ad\u653e<\/h3>\n\n\n\n<p>\u53ef\u8c03\u8282\u4e3a\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>0.8X<\/li>\n\n\n\n<li>1.0X<\/li>\n\n\n\n<li>1.2X<\/li>\n\n\n\n<li>1.5X<\/li>\n\n\n\n<li>2.0X<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u64ad\u653e\/\u6682\u505c\u63a7\u5236<\/h3>\n\n\n\n<p>\u53ef\u901a\u8fc7\u4ee5\u4e0b\u65b9\u5f0f\u63a7\u5236\u64ad\u653e\/\u6682\u505c<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u70b9\u51fb\u9875\u9762\u7a7a\u767d\u5904\uff08\u4e0d\u542b\u6b4c\u8bcd\u6240\u5728\u7684\u906e\u7f69\uff09<\/li>\n\n\n\n<li>\u7a7a\u683c\u952e<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">\u5b8c\u6574\u7684html\u4ee3\u7801<\/h1>\n\n\n\t\t<div class='wp-block-bch-code-highlight  align' id='bhcCodeHighlight-eeba116a-2' data-attributes='{&quot;cId&quot;:&quot;eeba116a-2&quot;,&quot;language&quot;:&quot;html&quot;,&quot;code&quot;:&quot;&lt;!DOCTYPE html&gt;\\n&lt;html lang=\\&quot;zh\\&quot;&gt;\\n&lt;head&gt;\\n  &lt;meta charset=\\&quot;UTF-8\\&quot; \\\/&gt;\\n  &lt;meta name=\\&quot;viewport\\&quot; content=\\&quot;width=device-width, initial-scale=1.0\\&quot;&gt;\\n  &lt;title&gt;\\u97f3\\u4e50\\u53ef\\u89c6\\u5316\\u64ad\\u653e\\u5668&lt;\\\/title&gt;\\n  &lt;style&gt;\\n    html, body {\\n      margin: 0;\\n      padding: 0;\\n      overflow: hidden;\\n      background: black;\\n      font-family: &#039;Segoe UI&#039;, sans-serif;\\n      user-select: none;\\n      color: white;\\n      height: 100vh;\\n      display: flex;\\n      flex-direction: column;\\n    }\\n\\n    canvas {\\n      display: block;\\n      flex: 1;\\n      cursor: pointer; \\\/* \\u63d0\\u793a\\u80cc\\u666f\\u662f\\u53ef\\u70b9\\u51fb\\u7684 *\\\/\\n    }\\n\\n    #controls {\\n      position: absolute;\\n      top: 15px;\\n      left: 15px;\\n      z-index: 10;\\n      display: flex;\\n      flex-wrap: wrap;\\n      gap: 8px;\\n      align-items: center;\\n    }\\n\\n    .effect-btn {\\n      background: linear-gradient(45deg, #444, #888);\\n      color: white;\\n      padding: 10px 16px;\\n      border: none;\\n      border-radius: 20px;\\n      cursor: pointer;\\n      font-size: 14px;\\n      transition: all 0.3s ease;\\n    }\\n\\n    .effect-btn:hover {\\n      background: linear-gradient(45deg, #00f2fe, #4facfe);\\n      color: black;\\n      transform: scale(1.1);\\n      box-shadow: 0 0 10px #00f2fe;\\n    }\\n\\n    .effect-btn.active {\\n      background: linear-gradient(45deg, #4facfe, #00f2fe);\\n      color: black;\\n      box-shadow: 0 0 15px #00f2fe;\\n    }\\n\\n    #fileInput {\\n      display: none; \\\/* \\u9ed8\\u8ba4\\u9690\\u85cf\\uff0c\\u53ef\\u4ee5\\u901a\\u8fc7\\u70b9\\u51fb\\u67d0\\u4e2a\\u6309\\u94ae\\u6765\\u89e6\\u53d1 *\\\/\\n    }\\n\\n    #bottomBar {\\n      position: absolute;\\n      bottom: 20px;\\n      left: 20px;\\n      right: 20px;\\n      z-index: 10;\\n      display: flex;\\n      align-items: center;\\n      gap: 10px;\\n    }\\n    \\n    #speedControls {\\n        display: flex;\\n        flex-wrap: wrap; \\\/* \\u5141\\u8bb8\\u901f\\u5ea6\\u6309\\u94ae\\u6362\\u884c *\\\/\\n        gap: 5px;\\n    }\\n\\n    #playPauseBtn {\\n      background: #222;\\n      color: white;\\n      border: none;\\n      padding: 8px 14px;\\n      border-radius: 12px;\\n      cursor: pointer;\\n      transition: 0.2s;\\n      flex-shrink: 0;\\n      width: 60px;\\n      font-size: 14px;\\n    }\\n\\n    #playPauseBtn:hover {\\n      background: #00f2fe;\\n      color: black;\\n    }\\n\\n    #progress {\\n      flex: 1;\\n      height: 8px;\\n      background: #555;\\n      border-radius: 5px;\\n      overflow: hidden;\\n      cursor: pointer;\\n    }\\n\\n    #progressFill {\\n      height: 100%;\\n      background: linear-gradient(to right, #00f2fe, #4facfe);\\n      width: 0%;\\n    }\\n\\n    .speed-btn {\\n      background: #222;\\n      color: white;\\n      border: none;\\n      padding: 6px 12px;\\n      border-radius: 12px;\\n      cursor: pointer;\\n      transition: 0.2s;\\n    }\\n\\n    .speed-btn:hover {\\n      background: #00f2fe;\\n      color: black;\\n    }\\n\\n    .speed-btn.active {\\n      background: #4facfe;\\n      color: black;\\n    }\\n\\n    #lyrics {\\n      position: absolute;\\n      right: 20px;\\n      top: 60px;\\n      bottom: 100px;\\n      width: 320px;\\n      overflow-y: auto;\\n      background: rgba(0, 0, 0, 0.6);\\n      border-radius: 10px;\\n      padding: 15px 20px;\\n      font-size: 20px;\\n      line-height: 1.6;\\n      color: #ccc;\\n      z-index: 10;\\n      scrollbar-width: thin;\\n      scrollbar-color: #555 #333;\\n    }\\n\\n    #lyrics .current {\\n      color: #00f2fe;\\n      font-weight: bold;\\n      font-size: 30px;\\n    }\\n\\n    #playPrompt {\\n      position: fixed;\\n      top: 50%;\\n      left: 50%;\\n      transform: translate(-50%, -50%);\\n      color: #00f2fe;\\n      font-size: 20px;\\n      text-align: center;\\n      background: rgba(0,0,0,0.8);\\n      padding: 20px 30px;\\n      border-radius: 12px;\\n      cursor: pointer;\\n      display: none;\\n      user-select: none;\\n      z-index: 20;\\n    }\\n\\n    \\\/* 1. \\u6539\\u8fdb\\uff1a\\u6dfb\\u52a0\\u5a92\\u4f53\\u67e5\\u8be2\\u4ee5\\u9002\\u5e94\\u624b\\u673a\\u5c4f\\u5e55 *\\\/\\n    @media (max-width: 768px) {\\n      #controls {\\n        top: 10px;\\n        left: 10px;\\n        right: 10px;\\n      }\\n      .effect-btn {\\n        padding: 8px 12px;\\n        font-size: 12px;\\n      }\\n\\n      #lyrics {\\n        top: 60px;\\n        left: 10px;\\n        right: 10px;\\n        bottom: 150px; \\\/* \\u4e3a\\u5e95\\u90e8\\u63a7\\u5236\\u680f\\u7559\\u51fa\\u66f4\\u591a\\u7a7a\\u95f4 *\\\/\\n        width: auto; \\\/* \\u5bbd\\u5ea6\\u81ea\\u52a8 *\\\/\\n        font-size: 14px;\\n        text-align: center;\\n      }\\n       #lyrics .current {\\n         font-size: 26px;\\n       }\\n\\n      #bottomBar {\\n        flex-direction: column; \\\/* \\u5782\\u76f4\\u6392\\u5217 *\\\/\\n        align-items: stretch; \\\/* \\u62c9\\u4f38\\u4ee5\\u586b\\u5145\\u5bbd\\u5ea6 *\\\/\\n        left: 10px;\\n        right: 10px;\\n        bottom: 10px;\\n        gap: 12px;\\n      }\\n      \\n      #speedControls {\\n        justify-content: center;\\n      }\\n    }\\n  &lt;\\\/style&gt;\\n&lt;\\\/head&gt;\\n&lt;body&gt;\\n\\n  &lt;div id=\\&quot;controls\\&quot;&gt;\\n    &lt;input type=\\&quot;file\\&quot; id=\\&quot;fileInput\\&quot; accept=\\&quot;audio\\\/*\\&quot; \\\/&gt;\\n    &lt;button class=\\&quot;effect-btn active\\&quot; data-effect=\\&quot;bars\\&quot;&gt;\\u6761\\u5f62\\u56fe&lt;\\\/button&gt;\\n    &lt;button class=\\&quot;effect-btn\\&quot; data-effect=\\&quot;wave\\&quot;&gt;\\u6ce2\\u6d6a\\u56fe&lt;\\\/button&gt;\\n    &lt;button class=\\&quot;effect-btn\\&quot; data-effect=\\&quot;circle\\&quot;&gt;\\u7ec4\\u4e50\\u5708&lt;\\\/button&gt;\\n    &lt;button class=\\&quot;effect-btn\\&quot; data-effect=\\&quot;particles\\&quot;&gt;\\u7c92\\u5b50\\u6ce1\\u6ce1&lt;\\\/button&gt;\\n  &lt;\\\/div&gt;\\n\\n  &lt;div id=\\&quot;bottomBar\\&quot;&gt;\\n    &lt;div style=\\&quot;display: flex; align-items: center; gap: 10px; width: 100%;\\&quot;&gt;\\n        &lt;button id=\\&quot;playPauseBtn\\&quot;&gt;\\u64ad\\u653e&lt;\\\/button&gt;\\n        &lt;div id=\\&quot;progress\\&quot;&gt;&lt;div id=\\&quot;progressFill\\&quot;&gt;&lt;\\\/div&gt;&lt;\\\/div&gt;\\n    &lt;\\\/div&gt;\\n    &lt;div id=\\&quot;speedControls\\&quot;&gt;\\n      &lt;button class=\\&quot;speed-btn\\&quot; data-speed=\\&quot;0.8\\&quot;&gt;0.8\\u00d7&lt;\\\/button&gt;\\n      &lt;button class=\\&quot;speed-btn active\\&quot; data-speed=\\&quot;1\\&quot;&gt;1\\u00d7&lt;\\\/button&gt;\\n      &lt;button class=\\&quot;speed-btn\\&quot; data-speed=\\&quot;1.2\\&quot;&gt;1.2\\u00d7&lt;\\\/button&gt;\\n      &lt;button class=\\&quot;speed-btn\\&quot; data-speed=\\&quot;1.5\\&quot;&gt;1.5\\u00d7&lt;\\\/button&gt;\\n      &lt;button class=\\&quot;speed-btn\\&quot; data-speed=\\&quot;2\\&quot;&gt;2\\u00d7&lt;\\\/button&gt;\\n    &lt;\\\/div&gt;\\n  &lt;\\\/div&gt;\\n\\n  &lt;canvas id=\\&quot;visualizer\\&quot;&gt;&lt;\\\/canvas&gt;\\n  &lt;audio id=\\&quot;audio\\&quot; crossorigin=\\&quot;anonymous\\&quot; style=\\&quot;display:none;\\&quot; loop&gt;&lt;\\\/audio&gt;\\n\\n  &lt;div id=\\&quot;lyrics\\&quot;&gt;&lt;\\\/div&gt;\\n  &lt;div id=\\&quot;playPrompt\\&quot;&gt;\\u70b9\\u6211\\u5f00\\u59cb\\u64ad\\u653e&lt;\\\/div&gt;\\n\\n  &lt;script&gt;\\n    \\\/\\\/ --- DOM\\u5143\\u7d20\\u83b7\\u53d6 (\\u4e0e\\u4e4b\\u524d\\u7248\\u672c\\u76f8\\u540c) ---\\n    const canvas = document.getElementById(&#039;visualizer&#039;);\\n    const ctx = canvas.getContext(&#039;2d&#039;);\\n    let W = window.innerWidth;\\n    let H = window.innerHeight;\\n    canvas.width = W;\\n    canvas.height = H;\\n\\n    const audio = document.getElementById(&#039;audio&#039;);\\n    const fileInput = document.getElementById(&#039;fileInput&#039;);\\n    const effectButtons = document.querySelectorAll(&#039;.effect-btn&#039;);\\n    const progress = document.getElementById(&#039;progress&#039;);\\n    const progressFill = document.getElementById(&#039;progressFill&#039;);\\n    const speedButtons = document.querySelectorAll(&#039;.speed-btn&#039;);\\n    const playPauseBtn = document.getElementById(&#039;playPauseBtn&#039;);\\n    const lyricsDiv = document.getElementById(&#039;lyrics&#039;);\\n    const playPrompt = document.getElementById(&#039;playPrompt&#039;);\\n\\n    let audioCtx, analyser, source, dataArray, bufferLength;\\n    let currentEffect = &#039;bars&#039;;\\n    let particles = [];\\n    let isPlaying = false;\\n    let currentLyricIndex = -1;\\n    let lyrics = [];\\n    \\n    \\\/\\\/ --- \\u97f3\\u9891\\u521d\\u59cb\\u5316\\u4e0e\\u64ad\\u653e\\u63a7\\u5236 ---\\n\\n    function initAudio() {\\n        if (audioCtx) return;\\n        audioCtx = new (window.AudioContext || window.webkitAudioContext)();\\n        analyser = audioCtx.createAnalyser();\\n        source = audioCtx.createMediaElementSource(audio);\\n        source.connect(analyser);\\n        analyser.connect(audioCtx.destination);\\n        analyser.fftSize = 256;\\n        bufferLength = analyser.frequencyBinCount;\\n        dataArray = new Uint8Array(bufferLength);\\n        particles = new Array(80).fill(0).map(() =&gt; ({\\n            x: Math.random() * W,\\n            y: Math.random() * H,\\n            r: Math.random() * 3 + 1,\\n            speed: Math.random() * 1 + 0.5\\n        }));\\n        render();\\n    }\\n\\n    async function startPlay() {\\n        if (!audio.src) {\\n            playPrompt.textContent = \\&quot;\\u8bf7\\u5148\\u9009\\u62e9\\u4e00\\u4e2a\\u97f3\\u9891\\u6587\\u4ef6\\&quot;;\\n            playPrompt.style.display = &#039;block&#039;;\\n            return;\\n        }\\n        if (!audioCtx) initAudio();\\n        if (audioCtx.state === &#039;suspended&#039;) await audioCtx.resume();\\n        try {\\n            await audio.play();\\n            playPrompt.style.display = &#039;none&#039;;\\n        } catch (err) {\\n            console.error(\\&quot;\\u64ad\\u653e\\u5931\\u8d25:\\&quot;, err);\\n            playPrompt.style.display = &#039;block&#039;;\\n        }\\n    }\\n\\n    \\\/\\\/ --- \\u4e8b\\u4ef6\\u76d1\\u542c ---\\n\\n    \\\/\\\/ \\u64ad\\u653e\\\/\\u6682\\u505c\\u6309\\u94ae\\u70b9\\u51fb\\n    playPauseBtn.onclick = () =&gt; {\\n        if (audio.paused) {\\n            startPlay();\\n        } else {\\n            audio.pause();\\n        }\\n    };\\n\\n    \\\/\\\/ 2. \\u6539\\u8fdb\\uff1a\\u70b9\\u51fbCanvas\\u7a7a\\u767d\\u5904\\u63a7\\u5236\\u64ad\\u653e\\\/\\u6682\\u505c\\n    canvas.addEventListener(&#039;click&#039;, () =&gt; {\\n        \\\/\\\/ \\u786e\\u4fdd\\u97f3\\u9891\\u5df2\\u52a0\\u8f7d\\uff0c\\u907f\\u514d\\u5728\\u65e0\\u97f3\\u9891\\u65f6\\u70b9\\u51fb\\u51fa\\u9519\\n        if (audio.src) {\\n            playPauseBtn.click(); \\\/\\\/ \\u89e6\\u53d1\\u4e3b\\u64ad\\u653e\\u6309\\u94ae\\u7684\\u70b9\\u51fb\\u4e8b\\u4ef6\\uff0c\\u590d\\u7528\\u903b\\u8f91\\n        }\\n    });\\n\\n    audio.onplay = () =&gt; {\\n        isPlaying = true;\\n        playPauseBtn.textContent = &#039;\\u6682\\u505c&#039;;\\n    };\\n\\n    audio.onpause = () =&gt; {\\n        isPlaying = false;\\n        playPauseBtn.textContent = &#039;\\u64ad\\u653e&#039;;\\n    };\\n    \\n    \\\/\\\/ \\u9875\\u9762\\u52a0\\u8f7d\\u540e\\u6267\\u884c\\n    window.onload = () =&gt; {\\n        audio.src = &#039;file\\\/\\u5b59\\u59ff\\u541f-\\u597d\\u4e45\\u6ca1\\u6709\\u4f60\\u7684\\u4fe1.mp3&#039;;\\n        loadLyrics(&#039;file\\\/\\u5b59\\u59ff\\u541f-\\u597d\\u4e45\\u6ca1\\u6709\\u4f60\\u7684\\u4fe1.lrc&#039;);\\n        startPlay().catch(() =&gt; {\\n            \\\/\\\/ \\u5373\\u4f7f\\u81ea\\u52a8\\u64ad\\u653e\\u5931\\u8d25\\uff0c\\u4e5f\\u8981\\u8ba9\\u63d0\\u793a\\u663e\\u793a\\u51fa\\u6765\\n            playPrompt.style.display = &#039;block&#039;;\\n        });\\n    };\\n    \\n    \\\/\\\/ \\u9009\\u62e9\\u6587\\u4ef6\\n    fileInput.addEventListener(&#039;change&#039;, (e) =&gt; {\\n        const file = e.target.files[0];\\n        if (file) {\\n            const url = URL.createObjectURL(file);\\n            audio.src = url;\\n            lyrics = []; \\\/\\\/ \\u6e05\\u7a7a\\u65e7\\u6b4c\\u8bcd\\n            lyricsDiv.innerHTML = &#039;&lt;div style=\\&quot;color:#666;\\&quot;&gt;\\u5c1d\\u8bd5\\u52a0\\u8f7d\\u540c\\u540d.lrc\\u6b4c\\u8bcd...&lt;\\\/div&gt;&#039;;\\n            \\\/\\\/ \\u5c1d\\u8bd5\\u52a0\\u8f7d\\u540c\\u540d\\u7684lrc\\u6587\\u4ef6\\n            const lrcFile = file.name.replace(\\\/\\\\.[^\\\/.]+$\\\/, \\&quot;\\&quot;) + \\&quot;.lrc\\&quot;;\\n            loadLyrics(lrcFile).catch(() =&gt; {\\n                lyricsDiv.innerHTML = &#039;&lt;div style=\\&quot;color:#666;\\&quot;&gt;\\u672a\\u627e\\u5230\\u6b4c\\u8bcd&lt;\\\/div&gt;&#039;;\\n            });\\n            startPlay();\\n        }\\n    });\\n    \\n    \\\/\\\/ \\u7a97\\u53e3\\u5927\\u5c0f\\u53d8\\u5316\\u65f6\\u8c03\\u6574\\n    window.onresize = () =&gt; {\\n      W = window.innerWidth;\\n      H = window.innerHeight;\\n      canvas.width = W;\\n      canvas.height = H;\\n    };\\n    \\n    \\\/\\\/ \\u521d\\u59cb\\u4ea4\\u4e92\\u63d0\\u793a\\n    playPrompt.addEventListener(&#039;click&#039;, startPlay);\\n    \\n    \\\/\\\/ \\u952e\\u76d8\\u7a7a\\u683c\\u952e\\u63a7\\u5236\\n    window.addEventListener(&#039;keydown&#039;, (e) =&gt; {\\n        if (e.code === &#039;Space&#039;) {\\n            e.preventDefault();\\n            playPauseBtn.click(); \\\/\\\/ \\u540c\\u6837\\u89e6\\u53d1\\u4e3b\\u6309\\u94ae\\u70b9\\u51fb\\n        }\\n    });\\n\\n    \\\/\\\/ --- \\u5176\\u4ed6\\u529f\\u80fd\\u51fd\\u6570 (\\u57fa\\u672c\\u4fdd\\u6301\\u4e0d\\u53d8) ---\\n    \\n    effectButtons.forEach(btn =&gt; {\\n      btn.onclick = (e) =&gt; {\\n        e.stopPropagation(); \\\/\\\/ \\u9632\\u6b62\\u70b9\\u51fb\\u6309\\u94ae\\u65f6\\u89e6\\u53d1canvas\\u7684\\u70b9\\u51fb\\u4e8b\\u4ef6\\n        currentEffect = btn.dataset.effect;\\n        effectButtons.forEach(b =&gt; b.classList.remove(&#039;active&#039;));\\n        btn.classList.add(&#039;active&#039;);\\n      };\\n    });\\n\\n    speedButtons.forEach(btn =&gt; {\\n      btn.onclick = (e) =&gt; {\\n        e.stopPropagation();\\n        const speed = parseFloat(btn.dataset.speed);\\n        audio.playbackRate = speed;\\n        speedButtons.forEach(b =&gt; b.classList.remove(&#039;active&#039;));\\n        btn.classList.add(&#039;active&#039;);\\n      };\\n    });\\n    \\n    progress.onclick = (e) =&gt; {\\n      e.stopPropagation();\\n      if (!audio.duration) return;\\n      const rect = progress.getBoundingClientRect();\\n      const clickX = e.clientX - rect.left;\\n      const ratio = clickX \\\/ rect.width;\\n      audio.currentTime = ratio * audio.duration;\\n    };\\n\\n    function updateProgress() {\\n      if (!audio.duration) return;\\n      const percent = (audio.currentTime \\\/ audio.duration) * 100;\\n      progressFill.style.width = percent + &#039;%&#039;;\\n    }\\n\\n    \\\/\\\/ --- \\u6e32\\u67d3\\u4e0e\\u53ef\\u89c6\\u5316 (\\u57fa\\u672c\\u4fdd\\u6301\\u4e0d\\u53d8) ---\\n\\n    function render() {\\n      requestAnimationFrame(render);\\n      if (!analyser) return;\\n      analyser.getByteFrequencyData(dataArray);\\n      ctx.clearRect(0, 0, W, H);\\n\\n      if (currentEffect === &#039;bars&#039;) drawBars();\\n      else if (currentEffect === &#039;wave&#039;) drawWave();\\n      else if (currentEffect === &#039;circle&#039;) drawCircle();\\n      else if (currentEffect === &#039;particles&#039;) drawSmoothParticles();\\n\\n      if (isPlaying) {\\n        updateProgress();\\n        highlightLyric(audio.currentTime);\\n      }\\n    }\\n\\n    function drawBars() {\\n      const barWidth = W \\\/ bufferLength;\\n      for (let i = 0; i &lt; bufferLength; i++) {\\n        const barHeight = dataArray[i] * (H \\\/ 255);\\n        ctx.fillStyle = `hsl(${i \\\/ bufferLength * 360}, 100%, 50%)`;\\n        ctx.fillRect(i * barWidth, H - barHeight, barWidth, barHeight);\\n      }\\n    }\\n\\n    function drawWave() {\\n      analyser.getByteTimeDomainData(dataArray);\\n      ctx.beginPath();\\n      ctx.lineWidth = 2;\\n      ctx.strokeStyle = &#039;#00ffcc&#039;;\\n      const sliceWidth = W \\\/ bufferLength;\\n      let x = 0;\\n      for (let i = 0; i &lt; bufferLength; i++) {\\n        let v = dataArray[i] \\\/ 128.0;\\n        let y = v * H \\\/ 2;\\n        if (i === 0) ctx.moveTo(x, y);\\n        else ctx.lineTo(x, y);\\n        x += sliceWidth;\\n      }\\n      ctx.stroke();\\n    }\\n\\n    function drawCircle() {\\n      let centerX = W \\\/ 2;\\n      let centerY = H \\\/ 2;\\n      let radius = Math.min(W, H) * 0.15;\\n      for (let i = 0; i &lt; bufferLength; i++) {\\n        let angle = (i \\\/ bufferLength) * 2 * Math.PI;\\n        let len = dataArray[i] * 0.8;\\n        let x1 = centerX + Math.cos(angle) * radius;\\n        let y1 = centerY + Math.sin(angle) * radius;\\n        let x2 = centerX + Math.cos(angle) * (radius + len);\\n        let y2 = centerY + Math.sin(angle) * (radius + len);\\n        ctx.strokeStyle = `hsl(${i \\\/ bufferLength * 360}, 100%, 50%)`;\\n        ctx.beginPath();\\n        ctx.moveTo(x1, y1);\\n        ctx.lineTo(x2, y2);\\n        ctx.stroke();\\n      }\\n    }\\n\\n    function drawSmoothParticles() {\\n      ctx.shadowColor = &#039;#00ffff&#039;;\\n      ctx.shadowBlur = 10;\\n      for (const p of particles) {\\n        let index = Math.floor((p.x \\\/ W) * bufferLength);\\n        let volume = dataArray[index] \\\/ 255;\\n        let size = p.r + volume * 12;\\n        p.y -= p.speed + volume * 2;\\n        if (p.y &lt; -size) {\\n          p.y = H + size;\\n          p.x = Math.random() * W;\\n        }\\n        ctx.beginPath();\\n        ctx.fillStyle = `rgba(0, 255, 255, ${0.3 + volume})`;\\n        ctx.arc(p.x, p.y, size, 0, Math.PI * 2);\\n        ctx.fill();\\n      }\\n      ctx.shadowBlur = 0;\\n    }\\n\\n    \\\/\\\/ --- \\u6b4c\\u8bcd\\u5904\\u7406 (\\u57fa\\u672c\\u4fdd\\u6301\\u4e0d\\u53d8) ---\\n    async function loadLyrics(url) {\\n        try {\\n            const res = await fetch(url);\\n            if (!res.ok) throw new Error(&#039;Network response was not ok&#039;);\\n            const text = await res.text();\\n            lyrics = parseLRC(text);\\n            renderLyrics();\\n        } catch (e) {\\n            console.warn(&#039;\\u6b4c\\u8bcd\\u52a0\\u8f7d\\u5931\\u8d25:&#039;, e);\\n            lyrics = [];\\n            lyricsDiv.innerHTML = &#039;&lt;div style=\\&quot;color:#666;\\&quot;&gt;\\u6682\\u65e0\\u6b4c\\u8bcd&lt;\\\/div&gt;&#039;;\\n        }\\n    }\\n    \\n    function parseLRC(text) {\\n        const lines = text.split(&#039;\\\\n&#039;);\\n        const result = [];\\n        const timeRegexp = \\\/\\\\[(\\\\d{2}):(\\\\d{2})\\\\.(\\\\d{2,3})\\\\]\\\/g;\\n        for (const line of lines) {\\n            const content = line.replace(timeRegexp, &#039;&#039;).trim();\\n            if (!content) continue;\\n            let match;\\n            timeRegexp.lastIndex = 0;\\n            while ((match = timeRegexp.exec(line)) !== null) {\\n                const min = parseInt(match[1], 10);\\n                const sec = parseInt(match[2], 10);\\n                const ms = parseInt(match[3].padEnd(3, &#039;0&#039;), 10);\\n                result.push({ time: min * 60 + sec + ms \\\/ 1000, text: content });\\n            }\\n        }\\n        return result.sort((a, b) =&gt; a.time - b.time);\\n    }\\n\\n    function renderLyrics() {\\n        lyricsDiv.innerHTML = lyrics.map(line =&gt; `&lt;div&gt;${line.text}&lt;\\\/div&gt;`).join(&#039;&#039;);\\n        currentLyricIndex = -1; \\\/\\\/ \\u91cd\\u7f6e\\u7d22\\u5f15\\n    }\\n\\n    function highlightLyric(currentTime) {\\n        if (!lyrics.length) return;\\n        let newIndex = lyrics.findIndex((line, index) =&gt; {\\n            const nextLine = lyrics[index + 1];\\n            return currentTime &gt;= line.time &amp;&amp; (nextLine ? currentTime &lt; nextLine.time : true);\\n        });\\n\\n        if (newIndex !== -1 &amp;&amp; newIndex !== currentLyricIndex) {\\n            if (lyricsDiv.children[currentLyricIndex]) {\\n                lyricsDiv.children[currentLyricIndex].classList.remove(&#039;current&#039;);\\n            }\\n            const currentLineEl = lyricsDiv.children[newIndex];\\n            currentLineEl.classList.add(&#039;current&#039;);\\n            \\\/\\\/ \\u81ea\\u52a8\\u6eda\\u52a8\\n            lyricsDiv.scrollTop = currentLineEl.offsetTop - lyricsDiv.clientHeight \\\/ 2 + currentLineEl.clientHeight \\\/ 2;\\n            currentLyricIndex = newIndex;\\n        }\\n    }\\n\\n  &lt;\\\/script&gt;\\n&lt;\\\/body&gt;\\n&lt;\\\/html&gt;&quot;,&quot;theme&quot;:&quot;twilight&quot;,&quot;codeTypo&quot;:{&quot;desktop&quot;:12,&quot;tablet&quot;:15,&quot;mobile&quot;:14},&quot;height&quot;:{&quot;desktop&quot;:&quot;300px&quot;,&quot;tablet&quot;:&quot;0px&quot;,&quot;mobile&quot;:&quot;0px&quot;},&quot;background&quot;:{&quot;color&quot;:&quot;#d3cfcf42&quot;,&quot;type&quot;:&quot;gradient&quot;,&quot;gradient&quot;:&quot;linear-gradient(135deg,rgb(69,39,164) 0%,rgb(32,10,10) 57%,rgb(131,68,197) 100%)&quot;},&quot;align&quot;:&quot;&quot;,&quot;lineNumbers&quot;:true,&quot;clipBoard&quot;:true,&quot;wordWrap&quot;:true,&quot;width&quot;:{&quot;desktop&quot;:&quot;100%&quot;,&quot;tablet&quot;:&quot;100%&quot;,&quot;mobile&quot;:&quot;100%&quot;},&quot;padding&quot;:{&quot;top&quot;:&quot;0px&quot;,&quot;right&quot;:&quot;0px&quot;,&quot;bottom&quot;:&quot;0px&quot;,&quot;left&quot;:&quot;0px&quot;},&quot;layout&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;border&quot;:{&quot;color&quot;:&quot;#000&quot;,&quot;style&quot;:&quot;solid&quot;,&quot;width&quot;:&quot;0px&quot;},&quot;shadow&quot;:[],&quot;alignment&quot;:&quot;center&quot;,&quot;clipBoardColors&quot;:{&quot;color&quot;:&quot;#fff&quot;,&quot;bg&quot;:&quot;#00000024&quot;}}'><\/div>\r\n\r\n\t\t\n\n\n<p>\u76ee\u5f55\u7ed3\u6784\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTML\u76ee\u5f55\/\n\u251c\u2500\u2500 index.html <em>\u2190 \u6587\u4ef6\u5185\u542b\u4ee5\u4e0ahtml\u4ee3\u7801<\/em>\n\u2514\u2500\u2500 file\/\n\u3000\u3000\u3000\u3000\u251c\u2500\u2500 \u5b59\u59ff\u541f-\u597d\u4e45\u6ca1\u6709\u4f60\u7684\u4fe1.mp3\n\u3000\u3000\u3000\u3000\u2514\u2500\u2500 \u5b59\u59ff\u541f-\u597d\u4e45\u6ca1\u6709\u4f60\u7684\u4fe1.lrc<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Demo \u622a\u56fe PC\u7248\u5e03\u5c40\uff1a \u624b\u673a\u7248\u5e03\u5c40\uff1a \u529f\u80fd\u4ecb\u7ecd \u97f3\u9891\u53ef\u89c6\u5316 \u6839\u636e\u97f3\u9891\u7684\u6ce2\u52a8\u5c55\u793a\u76f8\u5e94\u7684\u52a8\u6001\u753b\u9762\uff0c\u5305\u62ec\u56db\u79cd\u6548 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[160],"tags":[],"class_list":["post-3061","post","type-post","status-publish","format-standard","hentry","category-web"],"_links":{"self":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts\/3061","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3061"}],"version-history":[{"count":11,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts\/3061\/revisions"}],"predecessor-version":[{"id":3077,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts\/3061\/revisions\/3077"}],"wp:attachment":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3061"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3061"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3061"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}