一、效果展示
音频可视化vue组件版本
二、代码
<template>
<div class="audioPage" ref="audioPageRef" @click="palyMusic()">
<canvas ref="canvasRef" width="100" height="100"></canvas>
<audio ref="audioRef">
<source src="../../assets/audio/游戏城大冒险.mp3" />
</audio>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
const audioRef = ref();
const canvasRef = ref();
const audioPageRef = ref();
//需要可视化的数据
let audioArr: Uint8Array = new Uint8Array();
//我们需要一个音乐播放器和一个canvas进行显示
const radius = 20; // 圆的半径
let analyser: any = null;
//初始化
let isInit = false;
onMounted(() => {
drawGradientCircle(); // 绘制圆形
// 当音乐播放
audioRef.value.onplay = () => {
if (!isInit) {
isInit = true;
}
const audioContext = new AudioContext(); // 创建音频上下文
const audioSrc = audioContext.createMediaElementSource(audioRef.value); // 创建音频源
analyser = audioContext.createAnalyser(); // 创建分析器
analyser.fftSize = 64; // 设置傅里叶变换的大小,影响线条密度
audioArr = new Uint8Array(analyser.frequencyBinCount); // 创建一个无符号字节数组存储频率数据,该API参考ES6文档
audioSrc.connect(analyser); // 连接音频源和分析器
analyser.connect(audioContext.destination); // 连接分析器和音频目的地
//动画开始
animate();
};
});
// 绘制圆形
const drawGradientCircle = () => {
const ctx = canvasRef.value.getContext("2d");
const centerX = canvasRef.value.width / 2;
const centerY = canvasRef.value.height / 2;
ctx.beginPath();
// 创建一个从中心点向外的径向渐变
const grd = ctx.createLinearGradient(
centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius
);
grd.addColorStop("0", "purple");
grd.addColorStop("0.3", "magenta");
grd.addColorStop("0.5", "blue");
grd.addColorStop("0.6", "green");
grd.addColorStop("0.8", "yellow");
grd.addColorStop(1, "red");
ctx.strokeStyle = grd;
ctx.arc(centerX, centerY, radius - 2, 0, Math.PI * 2); // 绘制一个完整的圆
ctx.stroke(); // 画圆复制代码
};
//绘制线条
const drawLinesFromCircle = () => {
const ctx = canvasRef.value.getContext("2d");
const centerX = canvasRef.value.width / 2;
const centerY = canvasRef.value.height / 2;
ctx.lineWidth = 2;
//使用音频的频率数据绘制线条
//为了美观,我们绘制两条线,一条是频率数据,另一条是对称的
//也可以去使用其它的方式绘制线条
audioArr.forEach((value, index) => {
const baseAngle = (index / audioArr.length) * Math.PI * 2; // 基础角度
const angle1 = baseAngle; // 第一条线的角度
const angle2 = baseAngle + Math.PI; // 对称线的角度,相差π(180度)
// 绘制第一条线
{
const endX1 = centerX + radius * Math.cos(angle1);
const endY1 = centerY + radius * Math.sin(angle1);
const startX1 = centerX + (radius + value * 0.1) * Math.cos(angle1); // 使用value控制长度
const startY1 = centerY + (radius + value * 0.1) * Math.sin(angle1);
ctx.beginPath();
ctx.moveTo(startX1, startY1);
ctx.lineTo(endX1, endY1);
ctx.strokeStyle = `hsl(${index * 3.6}, 100%, 50%)`;
ctx.stroke();
}
// 绘制对称的第二条线
{
const endX2 = centerX + radius * Math.cos(angle2);
const endY2 = centerY + radius * Math.sin(angle2);
const startX2 = centerX + (radius + value * 0.1) * Math.cos(angle2);
const startY2 = centerY + (radius + value * 0.1) * Math.sin(angle2);
ctx.beginPath();
ctx.moveTo(startX2, startY2);
ctx.lineTo(endX2, endY2);
ctx.strokeStyle = `hsl(${
(index + audioArr.length / 2) * 3.6
}, 100%, 50%)`; // 调整颜色以保持对称性且有所区别
ctx.stroke();
}
});
};
//播放动画
const animate = () => {
const ctx = canvasRef.value.getContext("2d");
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height); // 清除画布
if (!isInit) return;
analyser.getByteFrequencyData(audioArr); // 获取频率数据
drawGradientCircle(); // 绘制圆形
drawLinesFromCircle(); // 绘制伸展的线条
requestAnimationFrame(animate); // 重复绘制以创建动画效果
};
const palyMusic = () => {
if (audioRef.value.paused) {
audioRef.value.play();
} else {
audioRef.value.pause();
}
};
</script>
<style scoped lang="scss">
.audioPage {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
</style>