Загрузка данных
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
background: transparent;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1920;
canvas.height = 1080;
let audioContext, analyser, dataArray, bufferLength;
let points = [];
const numPoints = 100; // Количество точек волны
const centerY = canvas.height / 2;
// Инициализация точек
for (let i = 0; i < numPoints; i++) {
points.push({
x: (canvas.width / (numPoints - 1)) * i,
y: centerY,
targetY: centerY,
velocity: 0
});
}
// Захват аудио
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
audioContext = new AudioContext();
analyser = audioContext.createAnalyser();
const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);
analyser.fftSize = 512;
analyser.smoothingTimeConstant = 0.85;
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
animate();
})
.catch(err => {
console.error('Ошибка доступа к микрофону:', err);
// Анимация без звука для теста
animate();
});
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (analyser) {
analyser.getByteFrequencyData(dataArray);
}
// Обновление целевых позиций точек на основе аудио
for (let i = 0; i < numPoints; i++) {
const point = points[i];
if (analyser) {
// Берем данные из разных частот для разных точек
const dataIndex = Math.floor((i / numPoints) * bufferLength);
const amplitude = dataArray[dataIndex] || 0;
// Нормализуем амплитуду (0-255 -> высота волны)
const normalizedAmplitude = (amplitude / 255) * 300;
// Добавляем синусоидальное движение для плавности
const wave = Math.sin(i * 0.1 + Date.now() * 0.003) * 20;
point.targetY = centerY + (Math.random() * normalizedAmplitude - normalizedAmplitude / 2) + wave;
} else {
// Тестовая анимация без звука
const wave = Math.sin(i * 0.2 + Date.now() * 0.002) * 50;
point.targetY = centerY + wave;
}
// Плавное движение к целевой позиции (пружинная физика)
const dy = point.targetY - point.y;
point.velocity += dy * 0.015; // Жесткость пружины
point.velocity *= 0.85; // Затухание
point.y += point.velocity;
}
// Рисование волны
drawWave();
// Рисование отражения (опционально)
drawReflection();
}
function drawWave() {
ctx.save();
// Основная волна
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
// Используем кривые Безье для плавности
for (let i = 0; i < points.length - 1; i++) {
const point = points[i];
const nextPoint = points[i + 1];
const controlX = point.x + (nextPoint.x - point.x) / 2;
const controlY1 = point.y;
const controlY2 = nextPoint.y;
ctx.quadraticCurveTo(controlX, controlY1, nextPoint.x, nextPoint.y);
}
// Стиль линии
ctx.strokeStyle = '#FFDB4D'; // Желтый как в Яндекс.Музыке
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Свечение
ctx.shadowBlur = 15;
ctx.shadowColor = '#FFDB4D';
ctx.stroke();
// Заливка под волной (опционально)
ctx.lineTo(canvas.width, canvas.height);
ctx.lineTo(0, canvas.height);
ctx.closePath();
const gradient = ctx.createLinearGradient(0, centerY - 150, 0, canvas.height);
gradient.addColorStop(0, 'rgba(255, 219, 77, 0.3)');
gradient.addColorStop(1, 'rgba(255, 219, 77, 0)');
ctx.fillStyle = gradient;
ctx.fill();
ctx.restore();
}
function drawReflection() {
ctx.save();
ctx.globalAlpha = 0.3;
ctx.scale(1, -1);
ctx.translate(0, -canvas.height);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 0; i < points.length - 1; i++) {
const point = points[i];
const nextPoint = points[i + 1];
const controlX = point.x + (nextPoint.x - point.x) / 2;
const controlY1 = point.y;
ctx.quadraticCurveTo(controlX, controlY1, nextPoint.x, nextPoint.y);
}
ctx.strokeStyle = '#FFDB4D';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.stroke();
ctx.restore();
}
// Адаптация под размер окна OBS
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Пересчет позиций точек
for (let i = 0; i < numPoints; i++) {
points[i].x = (canvas.width / (numPoints - 1)) * i;
}
});
</script>
</body>
</html>