代码拉取完成,页面将自动刷新
新建src/components/CountTo/index.vue
文件:
<script setup lang="ts">
import { PropType } from 'vue'
import { isNumber } from '@/utils/is'
import { propTypes } from '@/utils/propTypes'
const props = defineProps({
startVal: propTypes.number.def(0),
endVal: propTypes.number.def(2021),
duration: propTypes.number.def(3000),
autoplay: propTypes.bool.def(true),
decimals: propTypes.number.validate((value: number) => value >= 0).def(0),
decimal: propTypes.string.def('.'),
separator: propTypes.string.def(','),
prefix: propTypes.string.def(''),
suffix: propTypes.string.def(''),
useEasing: propTypes.bool.def(true),
easingFn: {
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
default(t: number, b: number, c: number, d: number) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
}
}
})
const emit = defineEmits(['mounted', 'callback'])
const formatNumber = (num: number | string) => {
const { decimals, decimal, separator, suffix, prefix } = props
num = Number(num).toFixed(decimals)
num += ''
const x = num.split('.')
let x1 = x[0]
const x2 = x.length > 1 ? decimal + x[1] : ''
const rgx = /(\d+)(\d{3})/
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2')
}
}
return prefix + x1 + x2 + suffix
}
const state = reactive<{
localStartVal: number
printVal: number | null
displayValue: string
paused: boolean
localDuration: number | null
startTime: number | null
timestamp: number | null
rAF: any
remaining: number | null
}>({
localStartVal: props.startVal,
displayValue: formatNumber(props.startVal),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null
})
const displayValue = toRef(state, 'displayValue')
onMounted(() => {
if (props.autoplay) {
start()
}
emit('mounted')
})
const getCountDown = computed(() => {
return props.startVal > props.endVal
})
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start()
}
})
const start = () => {
const { startVal, duration } = props
state.localStartVal = startVal
state.startTime = null
state.localDuration = duration
state.paused = false
state.rAF = requestAnimationFrame(count)
}
const pauseResume = () => {
if (state.paused) {
resume()
state.paused = false
} else {
pause()
state.paused = true
}
}
const pause = () => {
cancelAnimationFrame(state.rAF)
}
const resume = () => {
state.startTime = null
state.localDuration = +(state.remaining as number)
state.localStartVal = +(state.printVal as number)
requestAnimationFrame(count)
}
const reset = () => {
state.startTime = null
cancelAnimationFrame(state.rAF)
state.displayValue = formatNumber(props.startVal)
}
const count = (timestamp: number) => {
const { useEasing, easingFn, endVal } = props
if (!state.startTime) state.startTime = timestamp
state.timestamp = timestamp
const progress = timestamp - state.startTime
state.remaining = (state.localDuration as number) - progress
if (useEasing) {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
} else {
state.printVal = easingFn(
progress,
state.localStartVal,
endVal - state.localStartVal,
state.localDuration as number
)
}
} else {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
(state.localStartVal - endVal) * (progress / (state.localDuration as number))
} else {
state.printVal =
state.localStartVal +
(endVal - state.localStartVal) * (progress / (state.localDuration as number))
}
}
if (unref(getCountDown)) {
state.printVal = state.printVal < endVal ? endVal : state.printVal
} else {
state.printVal = state.printVal > endVal ? endVal : state.printVal
}
state.displayValue = formatNumber(state.printVal)
if (progress < (state.localDuration as number)) {
state.rAF = requestAnimationFrame(count)
} else {
emit('callback')
}
}
defineExpose({
pauseResume,
reset,
start,
pause
})
</script>
<template>
<span class="count-to">
{{ displayValue }}
</span>
</template>
这里封装了数字动画组件,方便在其他地方调用。
修改src/views/Functions/CountTo/index.vue
文件:
<script setup lang="ts" name="CountTo">
import useScrollPosition from '@/hooks/scrollPosition'
import useVkConfig from '@/hooks/vkConfig'
const store = useStore()
const route = useRoute()
const { vkConfig } = useVkConfig() // 获取项目动态配置参数、设备类型
// 滚动行为
useScrollPosition(route, store, vkConfig)
const countRef = ref(null)
const startVal = ref(0)
const endVal = ref(1314512)
const duration = ref(3000)
const decimals = ref(0)
const separator = ref(',')
const prefix = ref('¥ ')
const suffix = ref(' rmb')
const autoplay = ref(false)
const start = () => {
unref(countRef)?.start()
}
const pauseResume = () => {
unref(countRef)?.pauseResume()
}
const reset = () => {
unref(countRef)?.reset()
}
</script>
<template>
<el-space :size="vkConfig.space" fill direction="vertical">
<el-card shadow="never" class="anchor-wrap no-border no-radius">
<el-form inline>
<el-form-item label="起始值" label-width="70px"><el-input-number v-model="startVal" :min="0" :max="1000" :step="100" style="width: 160px" /></el-form-item>
<el-form-item label="最终值" label-width="70px"><el-input-number v-model="endVal" :min="1000" :max="9999999" :step="1000" style="width: 160px" /></el-form-item>
<el-form-item label="持续时间" label-width="70px"><el-input-number v-model="duration" :min="1000" :max="10000" :step="1000" style="width: 160px" /></el-form-item>
<el-form-item label="小数位数" label-width="70px"><el-input-number v-model="decimals" :min="0" :max="2" :step="1" style="width: 160px" /></el-form-item>
<el-form-item label="分隔符" label-width="70px"><el-input v-model="separator" style="width: 160px" /></el-form-item>
<el-form-item label="前缀" label-width="70px"><el-input v-model="prefix" style="width: 160px" /></el-form-item>
<el-form-item label="后缀" label-width="70px"><el-input v-model="suffix" style="width: 160px" /></el-form-item>
<el-form-item label="自动播放" label-width="70px"><el-switch v-model="autoplay" /></el-form-item>
<el-form-item>
<el-button type="primary" @click="start">动画开始</el-button>
<el-button type="primary" @click="pauseResume">动画暂停</el-button>
<el-button type="danger" @click="reset">重置动画</el-button>
<span class="tips">自动播放开启后,会在起始值、最终值发生变化时,自动执行数字动画</span>
</el-form-item>
</el-form>
<div class="element-wrap">
<CountTo :start-val="0" :end-val="10000" :duration="5000" :decimals="2" separator="," prefix="¥ " suffix=" RMB" :autoplay="true" class="text-30px font-bold text-[var(--el-color-primary)]" />
</div>
<div class="element-wrap">
<CountTo ref="countRef" :start-val="startVal" :end-val="endVal" :duration="duration" :decimals="decimals" :separator="separator" :prefix="prefix" :suffix="suffix" :autoplay="autoplay" class="text-30px font-bold text-[var(--el-color-primary)]" />
</div>
</el-card>
</el-space>
</template>
<style scoped lang="scss">
.el-space{
width: 100%;
padding: var(--el-space) var(--el-space) 0;
}
.no-border{
border: none;
}
.no-radius{
border-radius: 0;
}
.el-card{
display: flex;
:deep(.el-card__body){
display: flex;
flex: 1;
flex-direction: column;
}
}
.element-wrap{
position: relative;
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 14px;
overflow: hidden;
background-color: var(--el-color-primary-light-9);
}
.el-form{
.el-form-group{
float: left;
.el-form-item{
:deep(.el-form-item__content){
.el-textarea{
.el-textarea__inner{
height: 100%;
}
}
}
}
.el-button{
display: block;
margin-top: 30px;
margin-left: 6px;
}
.el-button+.el-button{
margin-top: 6px;
}
}
}
.tips{
margin-left: 10px;
color: var(--el-text-color-disabled);
}
</style>
这里展示了数字动画组件的使用方式,以及其所能传递的参数动态修改示例。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。