多年前看到别人jq写过一个这样的效果,感觉很神奇又炫酷,奈何那时候自己对这块的东西不了解,只能羡慕,今天站起来了要自己实现 vue3+ts 音频乐谱效果
先上效果图
效果演示
代码参考以及备注
<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>