vue3+ts实现音频乐谱效果

451 阅读2分钟

多年前看到别人jq写过一个这样的效果,感觉很神奇又炫酷,奈何那时候自己对这块的东西不了解,只能羡慕,今天站起来了要自己实现 vue3+ts 音频乐谱效果

先上效果图

image.png

image.png

效果演示

代码参考以及备注

<template>
  <div class="audio-box">
    <!-- Canvas元素用于绘制音频可视化图 -->
    <canvas ref="canvas" width="800" height="300"></canvas>
    <!-- 文件输入,允许用户选择音频文件 -->
    <input type="file" @change="handleFileChange" />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

// 引用 Canvas 元素
const canvas = ref<HTMLCanvasElement | null>(null)
let audioContext: AudioContext // 创建音频上下文
let analyser: AnalyserNode // 创建分析节点,用于分析音频数据
let dataArray: Uint8Array // 存储频谱数据
let bufferLength: number // 保存频谱数据的长度

// 绘制可视化图形的函数
const drawVisualizer = () => {
  if (!canvas.value || !analyser) return // 确保 Canvas 和分析器存在

  const ctx = canvas.value.getContext('2d') // 获取 2D 上下文
  if (!ctx) return // 如果上下文不存在,退出

  requestAnimationFrame(drawVisualizer) // 请求下一帧绘图,形成动画效果

  analyser.getByteFrequencyData(dataArray) // 获取当前音频的频谱数据

  ctx.fillStyle = '#000' // 设置背景色为黑色
  ctx.fillRect(0, 0, canvas.value.width, canvas.value.height) // 清空 Canvas

  const barWidth = (canvas.value.width / bufferLength) * 2.5 // 计算每个条形的宽度
  let barHeight
  let x = 0 // 条形绘制起始位置

  // 循环绘制频谱图
  for (let i = 0; i < bufferLength; i++) {
    barHeight = dataArray[i] // 获取当前频率的强度
    ctx.fillStyle = `rgb(${barHeight + 100},50,50)` // 设置条形的颜色
    // 绘制条形
    ctx.fillRect(
      x,
      canvas.value.height - barHeight / 2, // 条形底部 y 坐标
      barWidth, // 条形的宽度
      barHeight / 2 // 条形的高度
    )
    x += barWidth + 1 // 更新 x 坐标,以绘制下一个条形
  }
}

// 处理文件选择的函数
const handleFileChange = async (event: Event) => {
  const files = (event.target as HTMLInputElement).files // 获取选择的文件
  if (!files || files.length === 0) return // 确保有文件选择

  const file = files[0] // 取第一个文件
  const arrayBuffer = await file.arrayBuffer() // 将文件读取为 ArrayBuffer

  // 创建音频上下文和分析器
  if (!audioContext) {
    audioContext = new (window.AudioContext ||
      (window as any).webkitAudioContext)() // 初始化音频上下文
    analyser = audioContext.createAnalyser() // 创建分析器
    analyser.fftSize = 256 // 配置 FFT 大小
    bufferLength = analyser.frequencyBinCount // 获取频率数据的个数
    dataArray = new Uint8Array(bufferLength) // 创建存储频率数据的数组
  }

  const audioBuffer = await audioContext.decodeAudioData(arrayBuffer) // 解码音频数据
  const source = audioContext.createBufferSource() // 创建音频源
  source.buffer = audioBuffer // 将解码后的音频数据赋给源
  source.connect(analyser) // 将源连接到分析器
  analyser.connect(audioContext.destination) // 将分析器连接到音频输出(扬声器)
  source.start() // 播放音频

  drawVisualizer() // 开始绘制可视化图形
}

// 在组件挂载时调用
onMounted(() => {
  if (canvas.value) {
    drawVisualizer() // 初始化绘制可视化图形
  }
})
</script>

<style scoped>
.audio-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: #333; /* 背景色设置为深色 */
}

canvas {
  border: 1px solid #ccc; /* Canvas 边框样式 */
  margin-top: 20px; /* 上方的间距 */
}
</style>

至此,你又觉得自己行了代码注释都备注好了,麻烦小伙伴们别挨个问咯我行,你行,大家行~告别内卷,告别加班!!!