Spaces:
Running
Running
<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> |