自定义动画
如果你不了解 CSS 动画或 CSS 过渡,建议先学习相关内容。
延时
011011044055011044
点击查看代码
vue
<script setup>
import { ref } from "vue";
const number = ref(114514);
function switchNumber() {
number.value = Math.floor(Math.random() * 1000000);
}
// #region increaseDelay
const delay = ref(0.1);
const increase = ref(false);
const animationOptions = ({ testResults }) => {
if (!increase.value) return { delay: delay.value };
let count = 0;
return testResults.map((part) =>
part.map(() => ({ delay: count++ * delay.value }))
);
};
// #endregion increaseDelay
</script>
<template>
<div class="text-center">
<vue-to-counter-number
:value="number"
:animation-options="animationOptions"
/>
</div>
<hr />
<div class="flex gap-4">
<input class="border border-solid p-1" v-model="number" type="number" />
<button class="border border-solid p-1" @click="switchNumber">切换</button>
</div>
<div class="flex gap-4 mt-4 items-center">
<label class="inline-flex gap-1 border border-solid p-1">
延时(s):0
<input v-model="delay" type="range" min="0" max="3" step="0.1" />
{{ delay }}
</label>
<label class="inline-flex gap-1 border border-solid p-1">
延迟递增:
<input type="checkbox" v-model="increase" />
</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>
缓动
为了更容易观察缓动效果,可调整动画时长并调大了字体。
011
Easing functions provided by Motion build-in easing functions and easings.net .
点击查看代码
vue
<script setup>
import { ref } from "vue";
import EasingView from "./EasingView.vue";
import { steps } from "vue-to-counter";
const number = ref(1);
function switchNumber() {
number.value = Math.floor(Math.random() * 100);
}
const fontSize = ref(64);
const animationOptions = ref({
ease: "easeIn",
duration: 2,
});
</script>
<template>
<div class="text-center">
<vue-to-counter-number
class="font-bold"
:style="{
fontSize: fontSize + 'px',
lineHeight: 1.2,
}"
:value="number"
:animation-options="{
...animationOptions,
ease:
animationOptions.ease === 'steps' ? steps(4) : animationOptions.ease,
}"
/>
</div>
<hr />
<div class="flex gap-4">
<input class="border border-solid p-1" v-model="number" type="number" />
<button class="border border-solid p-1" @click="switchNumber">切换</button>
</div>
<div class="flex gap-4 mt-4">
<label class="inline-flex gap-1 border border-solid p-1">
字号
<input v-model="fontSize" type="range" min="1" max="128" />
{{ fontSize }}px
</label>
<label class="inline-flex gap-1 border border-solid p-1">
持续时间(s):0
<input
v-model="animationOptions.duration"
type="range"
min="0"
max="6"
step="0.5"
/>
{{ animationOptions.duration }}
</label>
</div>
<div class="flex gap-4 mt-4">
<div class="flex-none w-64 flex flex-col">
<select
v-model="animationOptions.ease"
@update:model-value="switchNumber"
class="w-full border border-solid p-1 self-start appearance-auto"
>
<optgroup label="Motion Build-in Easings">
<option value="linear">linear</option>
<option value="easeIn">easeIn</option>
<option value="easeOut">easeOut</option>
<option value="easeInOut">easeInOut</option>
<option value="anticipate">anticipate</option>
<option value="steps">steps(4) 模拟卡顿感</option>
</optgroup>
<optgroup label="easings.net Easing">
<option value="easeInQuad">easeInQuad</option>
<option value="easeOutQuad">easeOutQuad</option>
<option value="easeInOutQuad">easeInOutQuad</option>
<option value="easeInCubic">easeInCubic</option>
<option value="easeOutCubic">easeOutCubic</option>
<option value="easeInOutCubic">easeInOutCubic</option>
<option value="easeInQuart">easeInQuart</option>
<option value="easeOutQuart">easeOutQuart</option>
<option value="easeInOutQuart">easeInOutQuart</option>
<option value="easeInQuint">easeInQuint</option>
<option value="easeOutQuint">easeOutQuint</option>
<option value="easeInOutQuint">easeInOutQuint</option>
<option value="easeInSine">easeInSine</option>
<option value="easeOutSine">easeOutSine</option>
<option value="easeInOutSine">easeInOutSine</option>
<option value="easeInExpo">easeInExpo</option>
<option value="easeOutExpo">easeOutExpo</option>
<option value="easeInOutExpo">easeInOutExpo</option>
<option value="easeInCirc">easeInCirc</option>
<option value="easeOutCirc">easeOutCirc</option>
<option value="easeInOutCirc">easeInOutCirc</option>
<option value="easeInBack">easeInBack</option>
<option value="easeOutBack">easeOutBack</option>
<option value="easeInOutBack">easeInOutBack</option>
<option value="easeInElastic">easeInElastic</option>
<option value="easeOutElastic">easeOutElastic</option>
<option value="easeInOutElastic">easeInOutElastic</option>
<option value="easeInBounce">easeInBounce</option>
<option value="easeOutBounce">easeOutBounce</option>
<option value="easeInOutBounce">easeInOutBounce</option>
</optgroup>
</select>
<span class="text-xs">
Easing functions provided by
<a
href="https://motion.dev/docs/easing-functions#functions"
target="_blank"
>
Motion build-in easing functions
</a>
and
<a href="https://easings.net" target="_blank"> easings.net </a>
.
</span>
</div>
<easing-view :easing="animationOptions.ease" />
</div>
</template>
<style scoped></style>
vue
<script setup>
import { computed, toRefs } from "vue";
import {
linear,
easeIn,
easeOut,
easeInOut,
anticipate,
steps,
easeInQuad,
easeOutQuad,
easeInOutQuad,
easeInCubic,
easeOutCubic,
easeInOutCubic,
easeInQuart,
easeOutQuart,
easeInOutQuart,
easeInQuint,
easeOutQuint,
easeInOutQuint,
easeInSine,
easeOutSine,
easeInOutSine,
easeInExpo,
easeOutExpo,
easeInOutExpo,
easeInCirc,
easeOutCirc,
easeInOutCirc,
easeInBack,
easeOutBack,
easeInOutBack,
easeInElastic,
easeOutElastic,
easeInOutElastic,
easeInBounce,
easeOutBounce,
easeInOutBounce,
} from "vue-to-counter";
const BuildInEasingFunction = {
linear,
easeIn,
easeOut,
easeInOut,
anticipate,
steps,
easeInQuad,
easeOutQuad,
easeInOutQuad,
easeInCubic,
easeOutCubic,
easeInOutCubic,
easeInQuart,
easeOutQuart,
easeInOutQuart,
easeInQuint,
easeOutQuint,
easeInOutQuint,
easeInSine,
easeOutSine,
easeInOutSine,
easeInExpo,
easeOutExpo,
easeInOutExpo,
easeInCirc,
easeOutCirc,
easeInOutCirc,
easeInBack,
easeOutBack,
easeInOutBack,
easeInElastic,
easeOutElastic,
easeInOutElastic,
easeInBounce,
easeOutBounce,
easeInOutBounce,
};
const props = defineProps({
easing: String,
});
const { easing } = toRefs(props);
const easingFunction = computed(() => {
const easingName = easing.value;
let result = BuildInEasingFunction[easingName];
{
switch (easingName) {
case "steps":
result = result(4);
}
}
return result;
});
const pathData = computed(() => {
const easingFunctionValue = easingFunction.value;
const points = Array.from({ length: 101 }, (_, i) => i / 100);
return points
.map((t, i) => {
const x = t * 160;
const y = 120 - easingFunctionValue(t) * 120;
return `${i === 0 ? "M" : "L"}${x},${y}`;
})
.join(" ");
});
</script>
<template>
<svg
class="overflow-visible mt-2 w-32 border p-1"
width="160"
height="120"
viewBox="0 0 160 120"
>
<defs>
<linearGradient id="out" x1="0%" y1="100%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#ed556a"></stop>
<stop offset="30%" stop-color="#ed556a"></stop>
<stop offset="50%" stop-color="#7a7374"></stop>
<stop offset="100%" stop-color="#7a7374"></stop>
</linearGradient>
</defs>
<path :d="pathData" stroke="url(#out)" fill="none" stroke-width="3px" />
</svg>
</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>