236 lines
5.3 KiB
Vue
236 lines
5.3 KiB
Vue
<template>
|
|
<div class="slider-wrapper">
|
|
<!-- Slot for passing any audio element -->
|
|
<img
|
|
v-if="volumeValue == 0"
|
|
style="width: 25px; height: 25px;"
|
|
src="~/assets/image/music_muted.svg"
|
|
:title="t('unmuteSlider')"
|
|
@click="toggleMute()"
|
|
>
|
|
<img
|
|
v-else
|
|
style="width: 25px; height: 25px;"
|
|
src="~/assets/image/music.svg"
|
|
:title="t('muteSlider')"
|
|
@click="toggleMute()"
|
|
>
|
|
<div class="slider">
|
|
<input
|
|
id="gain-control-music"
|
|
ref="MusicSlider"
|
|
type="range"
|
|
min="0"
|
|
max="1"
|
|
step="0.001"
|
|
data-toggle="tooltip"
|
|
data-placement="top"
|
|
:title="t('textSlider')"
|
|
@input="changeVolumeOnTrack"
|
|
@wheel.prevent="changeVolumeOnWheel"
|
|
>
|
|
<span
|
|
class="slider-progress-bar"
|
|
:style="{ width: `${volumeValue * 100}%` }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { ref, watch, computed } from 'vue'
|
|
import { useAudioStore } from '~/stores/audio'
|
|
|
|
const MusicSlider = ref(null)
|
|
const audioStore = useAudioStore()
|
|
const { t } = useI18n()
|
|
|
|
// Computed volume value from the audio store
|
|
const volumeValue = computed(() => audioStore.getVolume)
|
|
// Stores volume before muting
|
|
let volumeOnMute = audioStore.getVolume
|
|
|
|
/**
|
|
* Toggles mute state:
|
|
* - If volume > 0: saves current volume and sets to 0
|
|
* - If volume == 0: restores saved volume
|
|
*/
|
|
const toggleMute = () => {
|
|
if (audioStore.getVolume !== 0) {
|
|
volumeOnMute = audioStore.getVolume
|
|
audioStore.setVolume(0)
|
|
} else {
|
|
audioStore.setVolume(volumeOnMute)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Syncs external slider DOM element with the reactive volume state
|
|
*/
|
|
watch(volumeValue, (newValue) => {
|
|
if (MusicSlider.value) {
|
|
const inputSlider = MusicSlider.value as HTMLInputElement
|
|
inputSlider.valueAsNumber = newValue
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Handles scroll wheel changes over the slider element to increment/decrement volume.
|
|
* Clamped between 0 and 1 with 0.02 steps.
|
|
*
|
|
* @param {WheelEvent} event - Scroll event on the volume slider
|
|
*/
|
|
const changeVolumeOnWheel = (event: WheelEvent) => {
|
|
const deltaY = event.deltaY
|
|
if (deltaY < 0) {
|
|
const volumeAdd = Math.min(1, volumeValue.value + 0.02)
|
|
if (volumeAdd <= 1) { audioStore.setVolume(volumeAdd) }
|
|
} else {
|
|
const volumeCut = Math.max(0, volumeValue.value - 0.02)
|
|
if (volumeCut >= 0) { audioStore.setVolume(volumeCut) }
|
|
}
|
|
audioStore.setVolume(volumeValue.value)
|
|
}
|
|
|
|
/**
|
|
* Sets volume when user moves the range input (slider)
|
|
*
|
|
* @param {InputEvent} event - Input change from range slider
|
|
*/
|
|
const changeVolumeOnTrack = (event: InputEvent) => {
|
|
const target = event.target as HTMLInputElement
|
|
const volume = Number(target.value)
|
|
if (volume >= 0 && volume <= 1) {
|
|
audioStore.setVolume(volume)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.slider-wrapper{
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin-bottom: 1.5em;
|
|
}
|
|
|
|
.slider {
|
|
position: relative;
|
|
width: 100%;
|
|
margin: 1em auto;
|
|
}
|
|
|
|
.slider-icon {
|
|
cursor: pointer;
|
|
}
|
|
|
|
@media only screen and (max-width: 576px) {
|
|
.slider{
|
|
max-width: 80%;
|
|
}
|
|
}
|
|
|
|
.slider-progress-bar {
|
|
position: absolute;
|
|
height: 10px;
|
|
background-color: #e9c046;
|
|
border-top-left-radius: 5px;
|
|
border-bottom-left-radius: 5px;
|
|
border-radius: 5px;
|
|
left: 0;
|
|
z-index: 0;
|
|
}
|
|
|
|
/* Allgemeine Einstellungen für den Slider */
|
|
input[type="range"] {
|
|
-webkit-appearance: none; /* Entfernt das Standard-Styling in Webkit-Browsern */
|
|
appearance: none; /* Entfernt Standard-Styling in anderen Browsern */
|
|
width: 100%;
|
|
height: 10px;
|
|
background: #fff;
|
|
border-radius: 5px;
|
|
outline: none;
|
|
cursor: pointer;
|
|
position: absolute;
|
|
z-index:1;
|
|
left: 0;
|
|
}
|
|
|
|
/* Styling für den Track in Webkit-Browsern */
|
|
input[type="range"]::-webkit-slider-runnable-track {
|
|
height: 10px;
|
|
background: transparent;
|
|
border-radius: 5px;
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
/* Styling für den Thumb in Webkit-Browsern */
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
height: 20px;
|
|
width: 20px;
|
|
background: #fff;
|
|
border-radius: 50%;
|
|
margin-top: -5px;
|
|
position: relative;
|
|
z-index: 10;
|
|
border: 2px solid #e9c046;
|
|
}
|
|
|
|
/* Styling für den Track in Firefox */
|
|
input[type="range"]::-moz-range-track {
|
|
height: 10px;
|
|
background: transparent;
|
|
border-radius: 5px;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Styling für den Thumb in Firefox */
|
|
input[type="range"]::-moz-range-thumb {
|
|
height: 20px;
|
|
width: 20px;
|
|
background-color: #e9c046;
|
|
border: none;
|
|
border-radius: 50%;
|
|
position: relative;
|
|
z-index: 1;
|
|
border: 2px solid #e9c046;
|
|
margin-top: -5px;
|
|
}
|
|
|
|
input[type="range"]::-moz-range-progress {
|
|
height: 10px;
|
|
background-color: #e9c046;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
/* Styling für den Track in Internet Explorer/Edge */
|
|
input[type="range"]::-ms-track {
|
|
height: 10px;
|
|
background: transparent;
|
|
border-color: transparent;
|
|
color: transparent;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
/* Styling für den Thumb in Internet Explorer/Edge */
|
|
input[type="range"]::-ms-thumb {
|
|
height: 20px;
|
|
width: 20px;
|
|
background: #f5f5f5;
|
|
border: none;
|
|
border-radius: 50%;
|
|
box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
|
|
position: relative;
|
|
z-index: 2;
|
|
border: 2px solid #e9c046;
|
|
}
|
|
|
|
/* Entfernt Ticks in IE */
|
|
input[type="range"]::-ms-ticks-after,
|
|
input[type="range"]::-ms-ticks-before {
|
|
display: none;
|
|
}
|
|
</style>
|