feat: add volume slider
This commit is contained in:
@@ -12,6 +12,10 @@
|
|||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<button id="play-btn" class="button">play</button>
|
<button id="play-btn" class="button">play</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="volume-slider-container">
|
||||||
|
<output id="current-volume-label" for="volume-slider">100%</output>
|
||||||
|
<input id="volume-slider" type="range" min="0" max="100" step="1" />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<img class="cat" src="/images/cat-0.png"></img>
|
<img class="cat" src="/images/cat-0.png"></img>
|
||||||
<img class="eeping-cat" src="/images/eeping-cat.png"></img>
|
<img class="eeping-cat" src="/images/eeping-cat.png"></img>
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
const playBtn = document.getElementById("play-btn");
|
const playBtn = document.getElementById("play-btn");
|
||||||
const catImg = document.getElementsByClassName("cat")[0];
|
const catImg = document.getElementsByClassName("cat")[0];
|
||||||
|
const volumeSlider = document.getElementById("volume-slider");
|
||||||
|
const currentVolumeLabel = document.getElementById("current-volume-label");
|
||||||
const clickAudio = new Audio("/audio/click.wav");
|
const clickAudio = new Audio("/audio/click.wav");
|
||||||
const clickReleaseAudio = new Audio("/audio/click-release.wav");
|
const clickReleaseAudio = new Audio("/audio/click-release.wav");
|
||||||
|
|
||||||
@@ -8,8 +10,10 @@ const CROSSFADE_INTERVAL_MS = 20;
|
|||||||
const AUDIO_DURATION_MS = 60000;
|
const AUDIO_DURATION_MS = 60000;
|
||||||
|
|
||||||
let isPlaying = false;
|
let isPlaying = false;
|
||||||
|
let isFading = false;
|
||||||
let currentAudio;
|
let currentAudio;
|
||||||
let volume = 0;
|
let maxVolume = 100;
|
||||||
|
let currentVolume = 0;
|
||||||
|
|
||||||
function playAudio() {
|
function playAudio() {
|
||||||
// add a random query parameter at the end to prevent browser caching
|
// add a random query parameter at the end to prevent browser caching
|
||||||
@@ -20,11 +24,11 @@ function playAudio() {
|
|||||||
};
|
};
|
||||||
currentAudio.onpause = () => {
|
currentAudio.onpause = () => {
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
volume = 0;
|
currentVolume = 0;
|
||||||
playBtn.innerText = "play";
|
playBtn.innerText = "play";
|
||||||
};
|
};
|
||||||
currentAudio.onended = () => {
|
currentAudio.onended = () => {
|
||||||
volume = 0;
|
currentVolume = 0;
|
||||||
playAudio();
|
playAudio();
|
||||||
};
|
};
|
||||||
currentAudio.volume = 0;
|
currentAudio.volume = 0;
|
||||||
@@ -40,33 +44,43 @@ function playAudio() {
|
|||||||
function pauseAudio() {
|
function pauseAudio() {
|
||||||
currentAudio.pause();
|
currentAudio.pause();
|
||||||
currentAudio.volume = 0;
|
currentAudio.volume = 0;
|
||||||
volume = 0;
|
currentVolume = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fadeIn() {
|
function fadeIn() {
|
||||||
|
isFading = true;
|
||||||
|
|
||||||
// volume ranges from 0 to 100, this determines by how much the volume number
|
// volume ranges from 0 to 100, this determines by how much the volume number
|
||||||
// should be incremented at every step of the fade in
|
// should be incremented at every step of the fade in
|
||||||
const volumeStep = 100 / (CROSSFADE_DURATION_MS / CROSSFADE_INTERVAL_MS);
|
const volumeStep =
|
||||||
|
maxVolume / (CROSSFADE_DURATION_MS / CROSSFADE_INTERVAL_MS);
|
||||||
const handle = setInterval(() => {
|
const handle = setInterval(() => {
|
||||||
volume += volumeStep;
|
currentVolume += volumeStep;
|
||||||
if (volume >= 100) {
|
if (currentVolume >= maxVolume) {
|
||||||
clearInterval(handle);
|
clearInterval(handle);
|
||||||
|
currentVolume = maxVolume;
|
||||||
|
isFading = false;
|
||||||
} else {
|
} else {
|
||||||
currentAudio.volume = volume / 100;
|
currentAudio.volume = currentVolume / 100;
|
||||||
}
|
}
|
||||||
}, CROSSFADE_INTERVAL_MS);
|
}, CROSSFADE_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fadeOut() {
|
function fadeOut() {
|
||||||
|
isFading = true;
|
||||||
|
|
||||||
// volume ranges from 0 to 100, this determines by how much the volume number
|
// volume ranges from 0 to 100, this determines by how much the volume number
|
||||||
// should be decremented at every step of the fade out
|
// should be decremented at every step of the fade out
|
||||||
const volumeStep = 100 / (CROSSFADE_DURATION_MS / CROSSFADE_INTERVAL_MS);
|
const volumeStep =
|
||||||
|
maxVolume / (CROSSFADE_DURATION_MS / CROSSFADE_INTERVAL_MS);
|
||||||
const handle = setInterval(() => {
|
const handle = setInterval(() => {
|
||||||
volume -= volumeStep;
|
currentVolume -= volumeStep;
|
||||||
if (volume <= 0) {
|
if (currentVolume <= 0) {
|
||||||
clearInterval(handle);
|
clearInterval(handle);
|
||||||
|
currentVolume = 0;
|
||||||
|
isFading = false;
|
||||||
} else {
|
} else {
|
||||||
currentAudio.volume = volume / 100;
|
currentAudio.volume = currentVolume / 100;
|
||||||
}
|
}
|
||||||
}, CROSSFADE_INTERVAL_MS);
|
}, CROSSFADE_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
@@ -102,4 +116,14 @@ playBtn.onclick = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
volumeSlider.oninput = () => {
|
||||||
|
maxVolume = volumeSlider.value;
|
||||||
|
currentVolumeLabel.textContent = `${maxVolume}%`;
|
||||||
|
if (!isFading) {
|
||||||
|
currentAudio.volume = maxVolume / 100;
|
||||||
|
currentVolume = maxVolume;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
volumeSlider.value = 100;
|
||||||
animateCat();
|
animateCat();
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
--text: #4c4f69;
|
--text: #4c4f69;
|
||||||
--surface0: #ccd0da;
|
|
||||||
--surface1: #bcc0cc;
|
--surface1: #bcc0cc;
|
||||||
--base: #eff1f5;
|
--base: #eff1f5;
|
||||||
|
--lavender: #7287fd;
|
||||||
|
|
||||||
|
--range-height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
@@ -11,6 +13,7 @@
|
|||||||
--text: #cdd6f4;
|
--text: #cdd6f4;
|
||||||
--surface1: #45475a;
|
--surface1: #45475a;
|
||||||
--base: #1e1e2e;
|
--base: #1e1e2e;
|
||||||
|
--lavender: #b4befe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,8 +23,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: calc(100% - 4rem - 1px);
|
width: calc(100% - 4rem - 2px);
|
||||||
height: calc(100vh - 4rem - 1px);
|
height: calc(100vh - 4rem - 2px);
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: var(--base);
|
background-color: var(--base);
|
||||||
@@ -35,6 +38,7 @@ h5,
|
|||||||
h6 {
|
h6 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@@ -65,7 +69,7 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
border: 1px solid var(--text);
|
border: 2px solid var(--text);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +116,46 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.volume-slider-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider-container > output {
|
||||||
|
color: var(--text);
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
||||||
|
input[type="range"] {
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border: 2px solid var(--text);
|
||||||
|
background-color: var(--base);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
|
height: var(--range-height);
|
||||||
|
-webkit-appearance: none;
|
||||||
|
color: #13bba4;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
width: var(--range-height);
|
||||||
|
height: var(--range-height);
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: var(--text);
|
||||||
|
box-shadow: -80px 0 0 80px var(--lavender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
border: 2px solid var(--text);
|
border: 2px solid var(--text);
|
||||||
background: var(--base);
|
background: var(--base);
|
||||||
|
Reference in New Issue
Block a user