Simple Scrolling Effects
VueToCounter provides the following components for scrolling different types of data:
- VueToNumber:
number
,bigint
- VueToDatetimeDuration:
Date
- VueToString:
string
You can get a nice scrolling effect with just the default configuration.
TIP
Click the button to observe the value changes.
Scrolling Numbers
Imagine a water meter in real life, it keeps scrolling to show the current water usage. VueToCounter can help you achieve this effect.

Click to view code
<script setup>
import { ref } from "vue";
const number = ref(114514);
function switchNumber() {
number.value = Math.floor(Math.random() * 1000000);
}
</script>
<template>
<div class="text-center">
<vue-to-counter-number :value="number" />
</div>
<hr />
<div class="flex gap-4">
<input class="border border-solid p-1" v-model="number" />
<button class="border border-solid p-1" @click="switchNumber">切换</button>
</div>
</template>
<style scoped></style>
<script setup>
import StackblitzLogo from "../assets/stackblitz-logo.svg";
import { ref, toRefs } from "vue";
import { getCodeStackblitzParams } from "./generate-stackblitz-params";
import sdk from "@stackblitz/sdk";
import packageInfo from "../../vue-to-counter/package.json";
const props = defineProps({
title: {
type: String,
required: true,
},
});
const { title } = toRefs(props);
const containerRef = ref();
function handleStackblitz() {
if (!containerRef.value) return;
const tabs = containerRef.value.querySelectorAll(".tabs > label");
const blocks = containerRef.value.querySelectorAll(".blocks > div code");
if (tabs.length !== blocks.length) {
window.alert("The number of tabs and code blocks should be the same.");
return;
}
const files = Array.from(tabs).map((tab, index) => ({
filename: tab.textContent.trim(),
content: blocks[index].textContent,
}));
const params = getCodeStackblitzParams(files, {
title: `${title.value} - vue-to-counter@${packageInfo.version}`,
});
sdk.openProject(params, {
openFile: "src/demo.vue",
});
}
</script>
<template>
<div ref="containerRef" class="demo-container">
<div class="flex relative">
<span class="flex-auto" />
<span
title="Open In Stackblitz"
class="inline-block p-1 cursor-pointer hover:outline hover:outline-[#1389FD] outline-1"
@click="handleStackblitz"
>
<img
class="h-4 w-4 pointer-events-none"
:src="StackblitzLogo"
alt="CodeSandbox Logo"
/>
</span>
</div>
<hr />
<slot />
</div>
</template>
<style lang="scss">
.demo-container {
@apply flex flex-col justify-center border p-4 rounded-lg mt-4 text-sm;
.vue-to-counter {
@apply font-mono text-4xl;
}
.custom-block {
@apply m-0;
}
}
</style>
Countdown Effect
I believe you have seen many countdowns, but most of them change abruptly without transitions. VueToCounter can make your countdown effects smoother.
Click to view code
<script setup>
import { ref } from "vue";
import { DurationPartType } from "vue-to-counter";
const from = ref("2024-12-01T00:00:00");
const to = ref("2024-12-31T00:05:30");
function switchDatetime() {
[from.value, to.value] = [to.value, from.value];
}
const precision = ref(DurationPartType.Second);
</script>
<template>
<div class="text-center">
<vue-to-counter-datetime-duration
:value="[from, to]"
:precision="precision"
/>
</div>
<hr />
<div class="flex gap-4">
<div>
<input
class="border border-solid p-1"
v-model="from"
type="datetime-local"
/>
~
<input
class="border border-solid p-1"
v-model="to"
type="datetime-local"
/>
</div>
<button class="border border-solid p-1" @click="switchDatetime">
切换
</button>
</div>
<div class="flex gap-4 mt-4">
<label>
时间范围精度
<select
class="border border-solid p-1 appearance-auto"
v-model="precision"
>
<option
v-for="precision in [
DurationPartType.Year,
DurationPartType.Month,
DurationPartType.Day,
DurationPartType.Hour,
DurationPartType.Minute,
DurationPartType.Second,
]"
:key="precision"
:value="precision"
>
{{ precision }}
</option>
</select>
</label>
</div>
</template>
<style scoped></style>
<script setup>
import StackblitzLogo from "../assets/stackblitz-logo.svg";
import { ref, toRefs } from "vue";
import { getCodeStackblitzParams } from "./generate-stackblitz-params";
import sdk from "@stackblitz/sdk";
import packageInfo from "../../vue-to-counter/package.json";
const props = defineProps({
title: {
type: String,
required: true,
},
});
const { title } = toRefs(props);
const containerRef = ref();
function handleStackblitz() {
if (!containerRef.value) return;
const tabs = containerRef.value.querySelectorAll(".tabs > label");
const blocks = containerRef.value.querySelectorAll(".blocks > div code");
if (tabs.length !== blocks.length) {
window.alert("The number of tabs and code blocks should be the same.");
return;
}
const files = Array.from(tabs).map((tab, index) => ({
filename: tab.textContent.trim(),
content: blocks[index].textContent,
}));
const params = getCodeStackblitzParams(files, {
title: `${title.value} - vue-to-counter@${packageInfo.version}`,
});
sdk.openProject(params, {
openFile: "src/demo.vue",
});
}
</script>
<template>
<div ref="containerRef" class="demo-container">
<div class="flex relative">
<span class="flex-auto" />
<span
title="Open In Stackblitz"
class="inline-block p-1 cursor-pointer hover:outline hover:outline-[#1389FD] outline-1"
@click="handleStackblitz"
>
<img
class="h-4 w-4 pointer-events-none"
:src="StackblitzLogo"
alt="CodeSandbox Logo"
/>
</span>
</div>
<hr />
<slot />
</div>
</template>
<style lang="scss">
.demo-container {
@apply flex flex-col justify-center border p-4 rounded-lg mt-4 text-sm;
.vue-to-counter {
@apply font-mono text-4xl;
}
.custom-block {
@apply m-0;
}
}
</style>
Strings Can Also Scroll
Have you seen the moving title on the homepage? Such effects can make your static web pages more lively.
TIP
Please note that the default configuration supports a maximum of 17 characters. If you need more characters, refer to How to Remove Character Length Limit.
Click to view code
<script setup>
import { ref } from "vue";
const string = ref("Hello, World!");
const strings = [
"Hello, World!",
"你好,世界!",
"こんにちは、世界!",
"안녕하세요, 세계!",
// 太长了
// "Bonjour, le monde!",
"Hallo, Welt!",
"Ciao, mondo!",
"Olá, mundo!",
"Привет, мир!",
"¡Hola, mundo!",
"Hej, världen!",
"Merhaba, Dünya!",
"مرحبا بالعالم!",
"שלום, עולם!",
"नमस्ते, दुनिया!",
"سلام دنیا!",
];
let stringIndex = 0;
function switchString() {
stringIndex = (stringIndex + 1) % strings.length;
string.value = strings[stringIndex];
}
</script>
<template>
<div class="text-center">
<vue-to-counter-string :value="string" />
</div>
<hr />
<div class="flex gap-4">
<input class="border border-solid p-1" v-model="string" />
<button class="border border-solid p-1" @click="switchString">切换</button>
</div>
</template>
<style scoped></style>
<script setup>
import StackblitzLogo from "../assets/stackblitz-logo.svg";
import { ref, toRefs } from "vue";
import { getCodeStackblitzParams } from "./generate-stackblitz-params";
import sdk from "@stackblitz/sdk";
import packageInfo from "../../vue-to-counter/package.json";
const props = defineProps({
title: {
type: String,
required: true,
},
});
const { title } = toRefs(props);
const containerRef = ref();
function handleStackblitz() {
if (!containerRef.value) return;
const tabs = containerRef.value.querySelectorAll(".tabs > label");
const blocks = containerRef.value.querySelectorAll(".blocks > div code");
if (tabs.length !== blocks.length) {
window.alert("The number of tabs and code blocks should be the same.");
return;
}
const files = Array.from(tabs).map((tab, index) => ({
filename: tab.textContent.trim(),
content: blocks[index].textContent,
}));
const params = getCodeStackblitzParams(files, {
title: `${title.value} - vue-to-counter@${packageInfo.version}`,
});
sdk.openProject(params, {
openFile: "src/demo.vue",
});
}
</script>
<template>
<div ref="containerRef" class="demo-container">
<div class="flex relative">
<span class="flex-auto" />
<span
title="Open In Stackblitz"
class="inline-block p-1 cursor-pointer hover:outline hover:outline-[#1389FD] outline-1"
@click="handleStackblitz"
>
<img
class="h-4 w-4 pointer-events-none"
:src="StackblitzLogo"
alt="CodeSandbox Logo"
/>
</span>
</div>
<hr />
<slot />
</div>
</template>
<style lang="scss">
.demo-container {
@apply flex flex-col justify-center border p-4 rounded-lg mt-4 text-sm;
.vue-to-counter {
@apply font-mono text-4xl;
}
.custom-block {
@apply m-0;
}
}
</style>
Next Steps
- To learn how to set language-sensitive number formats, date and time formats, check out Localization.
- If you want to customize the component appearance or transition animations, check out Custom Styles and Custom Animation.