Migrate to components
This commit is contained in:
108
client/src/pages/Game/components/MusicSlider/MusicSlider.jsx
Normal file
108
client/src/pages/Game/components/MusicSlider/MusicSlider.jsx
Normal file
@ -0,0 +1,108 @@
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import "./styles.sass";
|
||||
|
||||
export const MusicSlider = () => {
|
||||
const [frequency, setFrequency] = useState(440);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const audioContextRef = useRef(null);
|
||||
const oscillatorRef = useRef(null);
|
||||
const gainNodeRef = useRef(null);
|
||||
const sliderRef = useRef(null);
|
||||
|
||||
const handleMouseDown = (e) => {
|
||||
setDragging(true);
|
||||
startAudio();
|
||||
handleFrequencyChange(e);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setDragging(false);
|
||||
stopAudio();
|
||||
};
|
||||
|
||||
const handleFrequencyChange = (e) => {
|
||||
const rect = sliderRef.current.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const clampedX = Math.max(0, Math.min(x, rect.width));
|
||||
const newFrequency = 20 + (clampedX / rect.width) * 1980;
|
||||
setFrequency(newFrequency);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e) => dragging && handleFrequencyChange(e);
|
||||
const handleMouseUpGlobal = () => dragging && handleMouseUp();
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUpGlobal);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUpGlobal);
|
||||
};
|
||||
}, [dragging]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlaying) {
|
||||
if (oscillatorRef.current) {
|
||||
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
||||
}
|
||||
}
|
||||
}, [frequency, isPlaying]);
|
||||
|
||||
const startAudio = () => {
|
||||
if (!audioContextRef.current) {
|
||||
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
if (!oscillatorRef.current) {
|
||||
oscillatorRef.current = audioContextRef.current.createOscillator();
|
||||
oscillatorRef.current.type = 'sine';
|
||||
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
||||
|
||||
gainNodeRef.current = audioContextRef.current.createGain();
|
||||
gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime);
|
||||
|
||||
oscillatorRef.current.connect(gainNodeRef.current);
|
||||
gainNodeRef.current.connect(audioContextRef.current.destination);
|
||||
oscillatorRef.current.start();
|
||||
} else {
|
||||
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime);
|
||||
}
|
||||
setIsPlaying(true);
|
||||
};
|
||||
|
||||
const stopAudio = () => {
|
||||
if (oscillatorRef.current) {
|
||||
oscillatorRef.current.stop();
|
||||
oscillatorRef.current.disconnect();
|
||||
oscillatorRef.current = null;
|
||||
}
|
||||
setIsPlaying(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="otamatone-container">
|
||||
<div className="otamatone" onMouseDown={handleMouseDown}>
|
||||
<div className="otamatone-neck" ref={sliderRef}>
|
||||
<div className="frequency-indicator" style={{left: `${(frequency - 20) / 1980 * 100}%`}}></div>
|
||||
<div className="note-marker" style={{left: '10%', pointerEvents: 'none'}}>♩</div>
|
||||
<div className="note-marker" style={{left: '50%', pointerEvents: 'none'}}>♪</div>
|
||||
<div className="note-marker" style={{left: '90%', pointerEvents: 'none'}}>♫</div>
|
||||
</div>
|
||||
<div className="otamatone-face">
|
||||
<div
|
||||
className="otamatone-mouth"
|
||||
style={{
|
||||
height: `${10 + (frequency / 2000) * 40}px`,
|
||||
width: `${10 + (frequency / 2000) * 40}px`
|
||||
}}
|
||||
></div>
|
||||
<div className="otamatone-eyes">
|
||||
<div className="eye left-eye"></div>
|
||||
<div className="eye right-eye"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
1
client/src/pages/Game/components/MusicSlider/index.js
Normal file
1
client/src/pages/Game/components/MusicSlider/index.js
Normal file
@ -0,0 +1 @@
|
||||
export {MusicSlider as default} from "./MusicSlider.jsx";
|
82
client/src/pages/Game/components/MusicSlider/styles.sass
Normal file
82
client/src/pages/Game/components/MusicSlider/styles.sass
Normal file
@ -0,0 +1,82 @@
|
||||
.otamatone-container
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
position: fixed
|
||||
left: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
padding: 20px
|
||||
background-color: rgba(30, 30, 30, 0.5)
|
||||
backdrop-filter: blur(10px)
|
||||
border-radius: 20px
|
||||
margin: 20px
|
||||
z-index: 1
|
||||
|
||||
.otamatone
|
||||
display: flex
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
cursor: pointer
|
||||
padding: 10px
|
||||
width: 100%
|
||||
|
||||
.otamatone-neck
|
||||
flex: 1
|
||||
height: 20px
|
||||
background: linear-gradient(135deg, #000, #444)
|
||||
border-radius: 10px
|
||||
position: relative
|
||||
|
||||
.frequency-indicator
|
||||
position: absolute
|
||||
top: -10px
|
||||
width: 20px
|
||||
height: 20px
|
||||
background-color: #ff0000
|
||||
border-radius: 50%
|
||||
transform: translateX(-50%)
|
||||
|
||||
.note-marker
|
||||
position: absolute
|
||||
top: -30px
|
||||
font-size: 24pt
|
||||
color: #fff
|
||||
|
||||
.otamatone-face
|
||||
width: 100px
|
||||
height: 100px
|
||||
background: radial-gradient(circle, #fff, #ddd)
|
||||
border-radius: 50%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
position: absolute
|
||||
left: 0
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2)
|
||||
|
||||
.otamatone-eyes
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
width: 60%
|
||||
position: absolute
|
||||
top: 30px
|
||||
|
||||
.eye
|
||||
width: 15px
|
||||
height: 15px
|
||||
background-color: #000
|
||||
border-radius: 50%
|
||||
|
||||
.left-eye
|
||||
margin-right: 10px
|
||||
|
||||
.right-eye
|
||||
margin-left: 10px
|
||||
|
||||
.otamatone-mouth
|
||||
background-color: #000
|
||||
border-radius: 50%
|
||||
position: absolute
|
||||
bottom: 10px
|
Reference in New Issue
Block a user