Skip to content

Simple Scrolling Effects ​

VueToCounter provides the following components for scrolling different types of data:

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.

Water Meter
CC BY-SA 3.0, Link
CodeSandbox Logo

011011044055011044


Click to view code
vue
<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>
vue
<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.

CodeSandbox Logo


~

Click to view code
vue
<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>
vue
<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.

CodeSandbox Logo

0HH0ee0ll0ll0oo0,,0  0WW0oo0rr0ll0dd0!!


Click to view code
vue
<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>
vue
<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.