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.