Count wheel, behaves similarly to Twitter/Bluesky's metric counters
count-wheel.vue edited
99 lines 1.7 kB view raw
1<script setup lang="ts"> 2import { computed, ref, toRef, watch } from 'vue'; 3 4import { formatCompactNumber } from '~/lib/intl/number'; 5 6const props = defineProps<{ 7 value: number; 8}>(); 9 10const increasing = ref(false); 11const formatted = computed(() => formatCompactNumber(props.value)); 12 13watch(toRef(props, 'value'), (next, prev) => { 14 increasing.value = next > prev; 15}); 16</script> 17 18<template> 19 <span :class="$style.root"> 20 <Transition 21 :enter-active-class="increasing ? $style.slideUpEnter : $style.slideDownEnter" 22 :leave-active-class="increasing ? $style.slideUpLeave : $style.slideDownLeave" 23 > 24 <div :key="formatted">{{ formatted }}</div> 25 </Transition> 26 </span> 27</template> 28 29<style module> 30.root { 31 display: inline-block; 32 position: relative; 33 overflow: hidden; 34 text-overflow: ellipsis; 35 white-space: nowrap; 36} 37 38.slideUpEnter { 39 animation: slideUpEnter 300ms ease; 40} 41 42.slideUpLeave { 43 position: absolute; 44 animation: slideUpLeave 300ms ease; 45} 46 47@keyframes slideUpEnter { 48 from { 49 transform: translateY(100%); 50 opacity: 0; 51 } 52 to { 53 transform: translateY(0); 54 opacity: 100%; 55 } 56} 57 58@keyframes slideUpLeave { 59 from { 60 transform: translateY(0); 61 opacity: 100%; 62 } 63 to { 64 transform: translateY(-100%); 65 opacity: 0%; 66 } 67} 68 69.slideDownEnter { 70 animation: slideDownEnter 300ms ease; 71} 72 73.slideDownLeave { 74 position: absolute; 75 animation: slideDownLeave 300ms ease; 76} 77 78@keyframes slideDownEnter { 79 from { 80 transform: translateY(-100%); 81 opacity: 0; 82 } 83 to { 84 transform: translateY(0); 85 opacity: 100%; 86 } 87} 88 89@keyframes slideDownLeave { 90 from { 91 transform: translateY(0); 92 opacity: 100%; 93 } 94 to { 95 transform: translateY(100%); 96 opacity: 0%; 97 } 98} 99</style>