2 Star 28 Fork 10

kevin / 中后台管理系统

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
65功能-数字动画.md 8.57 KB
一键复制 编辑 原始数据 按行查看 历史

数字动画

新建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>

这里展示了数字动画组件的使用方式,以及其所能传递的参数动态修改示例。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/ctokevin/vue-admin-system.git
git@gitee.com:ctokevin/vue-admin-system.git
ctokevin
vue-admin-system
中后台管理系统
main

搜索帮助

344bd9b3 5694891 D2dac590 5694891