1、演示
2、代码分析
逐行解析 JavaScript 代码块:
const audioEle = document.querySelector('audio') const cvs = document.querySelector('canvas') const ctx = cvs.getContext('2d')
这几行代码首先获取了
<audio>
和<canvas>
元素的引用,并使用getContext('2d')
方法获取了 Canvas 2D 上下文对象,以便后续在画布上进行绘图操作。function initCvs() { cvs.width = window.innerWidth * devicePixelRatio cvs.height = (window.innerHeight / 2) * devicePixelRatio } initCvs()
initCvs
函数用于初始化画布的尺寸。它将画布的宽度设置为窗口宽度的倍数,高度设置为窗口高度的一半,同时乘以设备像素比devicePixelRatio
,以确保在不同设备上显示的效果一致。let isInit = false let dateArray = null let analyser = null
这几行定义了一些状态变量,用于跟踪音频分析器的初始化状态、频率数据数组、分析器对象以及音频播放状态。
audioEle.addEventListener('play', function (e) { if (isInit) return // 初始化 const audCtx = new AudioContext() // 创建音频上下文 const source = audCtx.createMediaElementSource(audioEle) // 创建音频源节点 analyser = audCtx.createAnalyser() analyser.fftSize = 512 // 设置 FFT 大小 dateArray = new Uint8Array(analyser.frequencyBinCount) // 创建存储频率数据的数组 source.connect(analyser) analyser.connect(audCtx.destination) isInit = true })
这段代码是一个事件监听器,当音频开始播放时触发。在此事件处理程序中:
- 首先检查是否已经初始化过分析器,如果是,则直接返回。
- 创建 AudioContext 对象
audCtx
,用于处理音频。- 使用
createMediaElementSource
方法创建音频源节点source
,将<audio>
元素作为输入。- 创建 AnalyserNode 对象
analyser
,用于分析音频频率数据。- 设置 AnalyserNode 的 fftSize 属性为 512,表示采样点数。
- 创建一个 Uint8Array 数组
dateArray
用于存储频率数据。- 将音频源节点连接到分析器节点,然后将分析器节点连接到 AudioContext 的目标节点。
- 最后设置状态变量
isInit
为 true,表示分析器已经初始化且音频正在播放。function draw() { requestAnimationFrame(draw) // 清空画布 const { width, height } = cvs ctx.clearRect(0, 0, width, height) if (!isInit && !isPlay) return // 获取频率数据并绘制波形图 analyser.getByteFrequencyData(dateArray) const len = dateArray.length / 2.5 ctx.fillStyle = '#266fff' const barWidth = width / len / 2 for (let i = 0; i < len; i++) { const data = dateArray[i] // < 256 const barHeight = (data / 255) * height const x1 = i * barWidth + width / 2 const x2 = width / 2 - (i + 1) * barWidth const y = height - barHeight ctx.fillRect(x1, y, barWidth - 2, barHeight) ctx.fillRect(x2, y, barWidth - 2, barHeight) } } draw()
draw
函数用于绘制波形图,通过requestAnimationFrame
实现动画效果。在函数中:
- 首先清空画布。
- 检查分析器是否已初始化并且音频正在播放,如果不是,则直接返回。
- 使用
analyser.getByteFrequencyData
方法获取频率数据,并存储在dateArray
中。- 计算每个柱状条的宽度和高度,并根据频率数据绘制柱状图形。
3、全部代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> body { background-color: #ffffff; display: flex; justify-content: center; align-items: center; flex-direction: column; } * { margin: 0; padding: 0; } canvas { border-bottom: 1px solid #266fff; } </style> </head> <body> <canvas></canvas> <audio src="./123.mp3" controls></audio> </body> <script> const audioEle = document.querySelector('audio') const cvs = document.querySelector('canvas') const ctx = cvs.getContext('2d') // 初始化canvas尺寸 function initCvs() { cvs.width = window.innerWidth * devicePixelRatio cvs.height = (window.innerHeight / 2) * devicePixelRatio } initCvs() // 初始化,只需要做一次就可以了 let isInit = false let dateArray = null let analyser = null audioEle.addEventListener('play', function (e) { if (isInit) return // 初始化 const audCtx = new AudioContext() // 创建音频上下文 const source = audCtx.createMediaElementSource(audioEle) // 创建音频源节点 // 什么叫音频源节点?节点其实就是音频处理的一个环节。音频可能有很多环节 比如说修音 比如说混响 比如说把音调高调低这些都是处理环节 // 每一个环节就是一个节点 在这些节点当中有一种叫源节点 表示我们音频数据的来源 analyser = audCtx.createAnalyser() analyser.fftSize = 512 // 2的n次幂。数值越大越细腻 // 创建数组 用于接收分析器节点的分析数据 dateArray = new Uint8Array(analyser.frequencyBinCount) // 数组里面的每一项都是一个无符号的8位整数 source.connect(analyser) analyser.connect(audCtx.destination) isInit = true }) // 把分析出的波形绘制到canvas上 function draw() { requestAnimationFrame(draw) // 清空画布 const { width, height } = cvs ctx.clearRect(0, 0, width, height) if (!isInit) return // 让分析器节点分析出数据到数组中 analyser.getByteFrequencyData(dateArray) const len = dateArray.length / 2.5 ctx.fillStyle = '#266fff' const barWidth = width / len / 2 for (let i = 0; i < len; i++) { const data = dateArray[i] // < 256 const barHeight = (data / 255) * height const x1 = i * barWidth + width / 2 const x2 = width / 2 - (i + 1) * barWidth const y = height - barHeight ctx.fillRect(x1, y, barWidth - 2, barHeight) ctx.fillRect(x2, y, barWidth - 2, barHeight) } } draw() </script> </html>