html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#upload {
position: fixed;
top: 0;
left: 0;
}
audio {
position: fixed;
top: 0;
right: 0;
}
<input type="file" id="upload" accept="audio/mpeg" />
const upload = document.querySelector("#upload");
upload.addEventListener("change", e => {
const [file] = e.target.files;
if (!file) upload.value = "";
const { type } = file;
if (!type.includes("audio")) {
alert("仅支持音频格式文件");
upload.value = "";
return false;
}
const url = URL.createObjectURL(file);
draw(url);
});
function draw(url) {
const { clientWidth: width, clientHeight: height } = document.body;
const audio = new Audio();
audio.src = url;
audio.controls = true;
audio.load();
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
document.body.appendChild(audio);
document.body.appendChild(canvas);
const canvasCtx = canvas.getContext("2d");
const audioCtx = new AudioContext();
const sourceNode = audioCtx.createMediaElementSource(audio);
// 音频分析器
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 128; // FFT
sourceNode.connect(analyser);
analyser.connect(audioCtx.destination);
const len = analyser.frequencyBinCount;
const buffer = new Uint8Array(len);
const gap = 2; // 间隙
const pillarWidth = width / len - gap;
const gradient = canvasCtx.createLinearGradient(0, 0, 0, 500);
gradient.addColorStop(0.8, "#1eec1e");
gradient.addColorStop(0.5, "#c97a3d");
gradient.addColorStop(0, "#f00f00");
const render = () => {
canvasCtx.fillStyle = "#fff";
canvasCtx.fillRect(0, 0, width, height);
analyser.getByteFrequencyData(buffer);
let x = 0;
buffer.forEach(v => {
const pillarHeight = (v / 255) * height;
canvasCtx.fillStyle = gradient;
canvasCtx.fillRect(
x,
height - (pillarHeight || 2),
pillarWidth,
pillarHeight || 2
);
x += pillarWidth + gap;
});
requestAnimationFrame(render);
};
render();
audio.play();
}