Audio-Visualizer / index.html
kolaslab's picture
Update index.html
f0cde8c verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Visualizer</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0f0f1a;
color: #fff;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow-x: hidden;
}
.container {
width: 100%;
min-height: 100vh;
padding: 20px;
position: relative;
}
header {
text-align: center;
padding: 40px 0;
animation: fadeIn 1s ease-in;
}
h1 {
font-size: 3em;
margin-bottom: 20px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.visualizer-container {
width: 100%;
height: calc(100vh - 200px);
margin: 0 auto;
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 20px;
position: relative;
}
canvas {
width: 100%;
height: 100%;
border-radius: 10px;
background: rgba(0, 0, 0, 0.3);
}
.controls {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
z-index: 100;
}
button {
padding: 10px 20px;
border: none;
border-radius: 25px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
color: white;
cursor: pointer;
transition: transform 0.2s;
}
button:hover {
transform: scale(1.05);
}
input[type="file"] {
display: none;
}
.visualization-styles {
position: fixed;
top: 20px;
right: 20px;
display: flex;
gap: 10px;
flex-direction: column;
z-index: 100;
}
.style-option {
padding: 8px 15px;
border-radius: 15px;
background: rgba(255, 255, 255, 0.1);
cursor: pointer;
transition: background 0.3s;
}
.style-option:hover {
background: rgba(255, 255, 255, 0.2);
}
.center-image {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 200px;
max-height: 200px;
z-index: 10;
}
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.3;
z-index: 1;
}
.fullscreen-btn {
position: fixed;
top: 20px;
left: 20px;
z-index: 100;
}
.credit {
position: fixed;
bottom: 10px;
right: 20px;
font-size: 14px;
opacity: 0.7;
z-index: 100;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
.visualizer-container {
padding: 10px;
}
h1 {
font-size: 2em;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Audio Visualizer</h1>
</header>
<div class="visualizer-container" id="visualizer-container">
<canvas id="visualizer"></canvas>
<img id="centerImage" class="center-image" style="display: none;">
<img id="backgroundImage" class="background-image" style="display: none;">
</div>
<div class="controls">
<button onclick="document.getElementById('audioInput').click()">Upload Audio</button>
<button onclick="document.getElementById('imageInput').click()">Upload Center Image</button>
<button onclick="document.getElementById('bgImageInput').click()">Upload Background</button>
<button id="playPause">Play</button>
<input type="file" id="audioInput" accept="audio/*">
<input type="file" id="imageInput" accept="image/*,video/gif">
<input type="file" id="bgImageInput" accept="image/*,video/gif">
</div>
<button class="fullscreen-btn" onclick="toggleFullscreen()">Fullscreen</button>
<div class="visualization-styles">
<div class="style-option" data-style="bars">Bars</div>
<div class="style-option" data-style="wave">Wave</div>
<div class="style-option" data-style="circle">Circle</div>
</div>
<div class="credit">by Софронов Артемий</div>
</div>
<script>
let audioContext, analyser, source;
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
let animationId;
let currentStyle = 'bars';
let centerImage = document.getElementById('centerImage');
let backgroundImage = document.getElementById('backgroundImage');
let bassValue = 0;
function resizeCanvas() {
canvas.width = canvas.clientWidth * window.devicePixelRatio;
canvas.height = canvas.clientHeight * window.devicePixelRatio;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
document.getElementById('audioInput').addEventListener('change', function(e) {
const file = e.target.files[0];
const audio = new Audio();
audio.src = URL.createObjectURL(file);
setupAudioContext(audio);
});
document.getElementById('imageInput').addEventListener('change', function(e) {
const file = e.target.files[0];
centerImage.src = URL.createObjectURL(file);
centerImage.style.display = 'block';
});
document.getElementById('bgImageInput').addEventListener('change', function(e) {
const file = e.target.files[0];
backgroundImage.src = URL.createObjectURL(file);
backgroundImage.style.display = 'block';
});
function setupAudioContext(audio) {
if (audioContext) audioContext.close();
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
source = audioContext.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 256;
audio.play();
draw();
}
function draw() {
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
function animate() {
animationId = requestAnimationFrame(animate);
analyser.getByteFrequencyData(dataArray);
ctx.fillStyle = 'rgba(15, 15, 26, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Get bass value for image animation
bassValue = dataArray.slice(0, 10).reduce((a, b) => a + b) / 10;
if (centerImage.style.display !== 'none') {
const scale = 1 + (bassValue / 255) * 0.2;
centerImage.style.transform = `translate(-50%, -50%) scale(${scale})`;
}
switch(currentStyle) {
case 'bars':
drawBars(dataArray, bufferLength);
break;
case 'wave':
drawWave(dataArray, bufferLength);
break;
case 'circle':
drawCircle(dataArray, bufferLength);
break;
}
}
animate();
}
function drawBars(dataArray, bufferLength) {
const barWidth = canvas.width / bufferLength;
for(let i = 0; i < bufferLength; i++) {
const barHeight = dataArray[i] * canvas.height / 255;
const gradient = ctx.createLinearGradient(0, canvas.height, 0, 0);
gradient.addColorStop(0, '#ff6b6b');
gradient.addColorStop(1, '#4ecdc4');
ctx.fillStyle = gradient;
ctx.fillRect(i * barWidth, canvas.height - barHeight, barWidth - 1, barHeight);
}
}
function drawWave(dataArray, bufferLength) {
ctx.beginPath();
ctx.strokeStyle = '#4ecdc4';
ctx.lineWidth = 2;
for(let i = 0; i < bufferLength; i++) {
const x = i * (canvas.width / bufferLength);
const y = (dataArray[i] / 128.0) * (canvas.height / 2);
if(i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
function drawCircle(dataArray, bufferLength) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 50;
ctx.beginPath();
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 2;
for(let i = 0; i < bufferLength; i++) {
const angle = i * 2 * Math.PI / bufferLength;
const amplitude = (dataArray[i] * radius / 255) + radius;
const x = centerX + amplitude * Math.cos(angle);
const y = centerY + amplitude * Math.sin(angle);
if(i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.stroke();
// Draw base circle
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.strokeStyle = 'rgba(255, 107, 107, 0.3)';
ctx.stroke();
}
document.querySelectorAll('.style-option').forEach(option => {
option.addEventListener('click', function() {
currentStyle = this.dataset.style;
});
});
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}
</script>
</body>
</html>