75 lines
1.4 KiB
Vue
75 lines
1.4 KiB
Vue
<script setup>
|
|
import { computed, watch } from 'vue'
|
|
import { useInterval } from '@vueuse/core'
|
|
|
|
const props = defineProps({
|
|
active: { type: Boolean, default: false },
|
|
seconds: { type: Number, default: 3 },
|
|
})
|
|
|
|
const emit = defineEmits(['finished'])
|
|
|
|
const { counter, pause, resume, reset } = useInterval(1000, {
|
|
controls: true,
|
|
immediate: false,
|
|
})
|
|
|
|
const remaining = computed(() => Math.max(props.seconds - counter.value, 0))
|
|
|
|
watch(
|
|
() => props.active,
|
|
(value) => {
|
|
if (value) {
|
|
reset()
|
|
resume()
|
|
return
|
|
}
|
|
|
|
pause()
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
watch(remaining, (value) => {
|
|
if (!props.active) return
|
|
|
|
if (value === 0) {
|
|
pause()
|
|
emit('finished')
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="active" class="countdown">
|
|
<span
|
|
v-motion
|
|
:initial="{ scale: 0.6, opacity: 0 }"
|
|
:enter="{ scale: 1, opacity: 1, transition: { type: 'spring', stiffness: 300, damping: 16 } }"
|
|
:key="remaining"
|
|
class="countdown-number"
|
|
>
|
|
{{ remaining }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.countdown {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 32px;
|
|
background: rgba(255, 230, 239, 0.65);
|
|
backdrop-filter: blur(2px);
|
|
}
|
|
|
|
.countdown-number {
|
|
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
|
font-weight: 700;
|
|
color: #8e2c4a;
|
|
}
|
|
</style>
|