第十二章 · 第一个应用

12.2 对话式开发全记录

本节最后更新:2026-05-13
验证环境:Node.js v22.x, Claude Code v1.x, 现代浏览器(Chrome/Firefox/Edge)

项目目标

我们要构建的"MP3-Visualization"应用包含四个功能:

本节展示真实的对话全过程。每轮对话后,你会看到 AI 做了什么、文件发生了什么变化。

第 1 轮:核心骨架

你的输入:

项目骨架已创建。现在请帮我在 index.html 中添加一个简单的界面布局——左侧是播放控制区,右侧是可视化画布。不需要花哨,能看就行。

AI 做了什么:

文件变化:

index.html 中多了一个两栏结构:

<div id="app">
  <div id="controls">
    <input type="file" id="fileInput" accept="audio/mp3">
    <button id="playBtn">播放</button>
    <input type="range" id="volumeSlider" min="0" max="1" step="0.01">
  </div>
  <canvas id="visualizer"></canvas>
</div>

你在这一轮的决策: 布局方案。你可能没有意识到,但你做了一个设计选择:左控制右画布。这个选择会影响后面所有功能的摆放位置。如果你之后发现"左边放控件空间不够",可以随时调整——AI 会帮你改。

第 2 轮:让音频动起来

你的输入:

现在添加一个实时频谱可视化效果。用户选择 MP3 文件后,画布上显示随音乐跳动的频谱柱状图。

AI 做了什么:

关键代码片段——这是 AI 写的最核心的部分:

const audioCtx = new AudioContext();
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 256;

function drawSpectrum() {
  requestAnimationFrame(drawSpectrum);
  const dataArray = new Uint8Array(analyser.frequencyBinCount);
  analyser.getByteFrequencyData(dataArray);

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  const barWidth = canvas.width / dataArray.length;

  dataArray.forEach((value, i) => {
    const barHeight = (value / 255) * canvas.height;
    ctx.fillStyle = `hsl(${i * 4}, 70%, 50%)`;
    ctx.fillRect(i * barWidth, canvas.height - barHeight, barWidth - 1, barHeight);
  });
}

你可能想问的问题:

这段代码中每一行都在做什么?如果你好奇,可以问 AI:

"解释一下 drawSpectrum 函数中每一行是做什么的。"

AI 会逐行解释:requestAnimationFrame 创建动画循环、getByteFrequencyData 获取当前帧的频谱数据、fillRect 在画布上绘制柱状条。

你不需要现在理解每一行。但如果你愿意,每轮对话后花 30 秒问问 AI "这段代码的关键逻辑是什么",你会逐渐积累对代码的理解。

优化方向:

柱状图跑起来之后,你可以继续优化。比如:

"柱状图的颜色用 HSL 色环过渡,从青色到紫色到红色。"

AI 会修改 fillStyle 的计算逻辑,颜色从低频到高频自然过渡。

"柱子之间留一点间隙,看起来更清晰。"

AI 会在 fillRect 的宽度参数上减 1 或 2 像素。

第 3 轮:添加歌词同步

你的输入:

我想加一个歌词功能。用户在左侧文本框中输入歌词,每行前面标注时间戳(格式 mm:ss),播放时高亮显示当前时间对应的歌词行。

AI 做了什么:

关键代码——歌词解析器:

function parseLyrics(text) {
  const lines = text.split('\n');
  return lines.map(line => {
    const match = line.match(/\[(\d{2}):(\d{2})\](.*)/);
    if (match) {
      return {
        time: parseInt(match[1]) * 60 + parseInt(match[2]),
        text: match[3].trim()
      };
    }
    return null;
  }).filter(l => l !== null);
}

你的"创意贡献":

AI 生成的歌词解析器默认按行匹配。你可以提出一个改进:

"如果歌词时间戳不是严格递增的,能否按最接近当前时间的行匹配?"

这个需求 AI 不会自己想到——因为这是你的使用场景决定的。这就是你在 Vibe Coding 中真正贡献的价值。

AI 会修改匹配逻辑:不再是"当前时间等于某行的时间戳才高亮",而是"在已排序的歌词行中,找到最接近当前时间的行"。即使时间戳不严格递增,也能正确工作。

第 4 轮:录制视频

你的输入:

最后加一个录制功能。点击"录制"按钮,将 Canvas 上的可视化效果和音频一起录制成 WebM 视频文件。

AI 做了什么:

关键代码——录制核心:

let mediaRecorder;
let recordedChunks = [];

function startRecording() {
  const canvasStream = canvas.captureStream(30);
  const audioDest = audioCtx.createMediaStreamDestination();
  audioSource.connect(audioDest);
  const combinedStream = new MediaStream([
    ...canvasStream.getVideoTracks(),
    ...audioDest.stream.getAudioTracks()
  ]);

  mediaRecorder = new MediaRecorder(combinedStream);
  recordedChunks = [];

  mediaRecorder.ondataavailable = (e) => recordedChunks.push(e.data);
  mediaRecorder.onstop = () => {
    const blob = new Blob(recordedChunks, { type: 'video/webm' });
    const url = URL.createObjectURL(blob);
    // 创建下载链接
  };

  mediaRecorder.start();
}

你可能遇到并需要修复的问题:

"录制下来的视频没有声音。"

这是一个很常见的问题。AI 会分析发现 audio 元素没有正确地连接到 MediaStreamDestination,导致录制时只捕获了画面没有捕获声音。修复后视频就正常了。

第 5 轮(新增):添加播放列表功能

随着功能增加,你可能想管理多个音频文件:

"添加一个播放列表功能。用户选择的 MP3 文件显示在列表中,可以点击切换,当前播放的歌曲高亮显示。"

AI 做了什么:

你在这个阶段的思考:

"播放列表中的歌曲播放完后,自动播放下一首。"

AI 会添加 audio.onended 事件监听,自动播放下一个。

"播放列表支持拖拽排序。"

AI 实现 HTML5 拖拽 API。不过你会发现在纯 HTML5 项目中实现拖拽排序比想象中复杂——AI 生成的代码可能第一次不够完美,需要几轮调整。

第 6 轮(新增):键盘快捷键

"添加键盘快捷键:空格键播放/暂停,左右方向键快进/后退 5 秒,上下方向键调节音量。"

AI 做了什么:

这个功能的代码量不大(大约 30 行),但对用户体验的提升很明显——用户不需要用鼠标点击按钮,键盘操作更快捷。

第 7 轮(新增):保存和加载配置

"添加一个保存配置的功能。用户调整的参数(音量、可视化模式、颜色方案)在刷新页面后不会丢失。"

AI 做了什么:

function saveConfig() {
  const config = {
    volume: volumeSlider.value,
    visualMode: currentMode,
    colorScheme: currentColorScheme
  };
  localStorage.setItem('mp3-visualizer-config', JSON.stringify(config));
}

function loadConfig() {
  const saved = localStorage.getItem('mp3-visualizer-config');
  if (saved) {
    const config = JSON.parse(saved);
    volumeSlider.value = config.volume;
    currentMode = config.visualMode;
    // 恢复各配置项
  }
}

完整对话是怎样的

在实际操作中,你的对话可能像这样:

你:我想做一个 MP3 音频可视化应用,HTML5 项目。

AI:好的,我创建了 index.html、style.css 和 app.js……

你:布局改成左右分栏,左边控件右边画布。

AI:已修改布局……

你:频谱柱状图太普通了,改成圆形辐射状效果。

AI:已改成圆形辐射状可视化……

你:录制出来的视频没有声音。

AI:检查发现 audio 元素没有连接到 MediaStream,修复了……

你:歌词高亮延迟了半秒,调整一下。

AI:将时间戳匹配逻辑从逐行扫描改为二分查找……

你:加一个播放列表,支持多文件管理和自动下一首。

AI:已添加播放列表功能……

你:界面右上角加一个模式切换按钮,点击在三种可视化效果间切换。

AI:已添加模式切换……

你:添加键盘快捷键,空格播放/暂停,左右方向键快进后退。

AI:已添加键盘快捷键……

你:刷新页面后设置都丢了,用 localStorage 保存用户配置。

AI:已添加配置持久化……

注意看——上面每一行对话都对应一个真实的开发决策。有些是设计决策(布局、视觉效果),有些是功能决策(录制、歌词、播放列表),有些是调试(声音问题、延迟问题),有些是体验优化(快捷键、配置保存)。你的角色是"拍板的人",AI 是"执行的人"。

关于"对话式开发"的几个关键认知

认知一:你的指令越具体,AI 第一次就越接近你的目标。

比较两个指令:

❌ "帮我搞一下可视化。"——AI 不知道你要什么效果、什么风格、什么交互方式。

✅ "在右侧画布上添加频谱柱状图效果,柱状条用渐变色,从青色(低频)过渡到紫色(中频)再到红色(高频)。"——AI 生成的代码第一次就接近你的目标。

你不一定每次都能给出具体指令——有时候你确实不知道自己想要什么。没关系,先让 AI 给一个默认方案,你说"改成另一种风格"就可以。

认知二:先让 AI 给出默认方案,再迭代优化,比一开始就追求完美高效得多。

这条规则不仅适用于代码,也适用于 UI、交互方式、技术选型。AI 的第一次输出通常是"安全的选择"——主流的技术方案、常见的 UI 模式。你先接受这个默认方案,然后基于实际的体验感受去改进。

认知三:有些需求 AI 不会自己想到,需要你提出来。

播放列表自动切歌、键盘快捷键、刷新后保持配置——这些"体验细节"AI 不会主动做,因为第一个版本的核心功能才是它的优先关注点。你在使用过程中的"不爽"——"为什么每次都要重新调音量"——就是最宝贵的产品反馈来源。把它变成一句话告诉 AI,它就帮你改了。

本节要点
Vibe 练习

对 Claude Code 说:

"我想复现这个 MP3 可视化项目。帮我创建一个包含音频播放、频谱可视化和录制功能的应用,不要求歌词功能。请逐步实现,每一步完成后让我确认再继续。"

进阶练习:

完成基础功能后,提出一个自定义需求:比如"在播放列表上方添加一个搜索框,可以过滤列表中的文件名"。观察 AI 如何理解"搜索框 + 过滤"这个新需求并集成到现有界面中。注意:这个需求在之前的对话中没有出现过——是你自己提出的新功能。