当前位置: 首页 > ops >正文

Vue3通过自定义指令实现数字滚动动画效果

文章目录

  • 前言
  • 一、什么是数字滚动效果?
  • 二、实现方式
  • 三、vue3自定义指令使用回顾
    • 3.1 生命周期钩子
      • 周期函数入参说明
    • 3.2 全局与局部指令注册
    • 3.3 参数、修饰符与动态绑定
      • 1、 指令参数(Arg)
      • 2 修饰符(Modifiers)
      • 3 动态参数
    • 四、数字滚屏功能设计和代码实现解析
      • 1、功能设计
      • 2、代码实现解析
    • 五、完整代码
  • 总结


前言

        数字滚动效果是前端开发中常见的交互需求,常用于数据可视化、计数器、统计面板等场景。本文将详细介绍如何在vue3中 实现流畅的数字滚动动画效果


一、什么是数字滚动效果?

        数字滚动效果是指让数字从一个值平滑过渡到另一个值的动画效果,通常表现为数字逐位递增或递减,模拟机械计数器的滚动效果。这种效果可以让数据展示更加生动,提升用户体验。

请添加图片描述

二、实现方式

在 Vue 中实现数字滚动效果主要有几种方式:

1、通过自定义组件实现
2、通过自定义指令实现

本文将重点介绍基于vue3自定义指令实现方案,这是最优雅、使用最简单的方式


三、vue3自定义指令使用回顾

在 Vue3 的生态体系中,自定义指令(Directive)是一个强大的工具,允许开发者直接操作 DOM 元素,实现组件难以覆盖的底层逻辑。无论是表单焦点管理、数据可视化还是性能优化(如图片懒加载),自定义指令都能提供优雅的解决方案。

3.1 生命周期钩子

钩子函数调用时机常用场景
created绑定元素的 attribute 或事件监听器应用之前初始化操作,事件监听器设置
beforeMount指令第一次绑定到元素时初始DOM操作前准备工作
mounted绑定元素插入父DOM后主要的DOM操作
beforeUpdate组件更新前更新前清理工作
updated组件及子组件更新后更新后DOM操作
beforeUnmount卸载绑定元素前清理定时器、事件监听等
unmounted指令与元素解绑后最终清理操作

需要注意的是beforeUpdate、updated触发时机是指令绑定的dom所在的组件更新时触发,而非指令绑定值改变时触发,即使不改变值也可能触发。

示例:

app.directive('focus', {// 在绑定元素的 attribute 前// 或事件监听器应用前调用created(el, binding, vnode) {// 下面会介绍各个参数的细节},// 在元素被插入到 DOM 前调用beforeMount(el, binding, vnode) {},// 在绑定元素的父组件// 及他自己的所有子节点都挂载完成后调用mounted(el, binding, vnode) {},// 绑定元素的父组件更新前调用beforeUpdate(el, binding, vnode, prevVnode) {},// 在绑定元素的父组件// 及他自己的所有子节点都更新后调用updated(el, binding, vnode, prevVnode) {},// 绑定元素的父组件卸载前调用beforeUnmount(el, binding, vnode) {},// 绑定元素的父组件卸载后调用unmounted(el, binding, vnode) {}
}
});

周期函数入参说明

  • el:指令绑定的 DOM 元素,能用于直接操作 DOM

  • binding:是一个对象,包含以下属性:
    1、 value:指令的绑定值,比如在v-my-directive="1 + 1"中,value 的值就是 2。
    2、 oldValue:指令绑定的前一个值,仅在update和beforeUpdate钩子中可用,无论值是否发生变化都会被传递。
    3、 arg: 传递给指令的参数,例如在v-my-directive:foo中,arg 的值就是 “foo”。
    4、 modifiers:一个包含修饰符的对象,例如在v-my-directive.foo.bar中,modifiers 对象就是{ foo: true, bar: true }。

  • vnode:当前虚拟节点

  • prevNode:上一个虚拟节点,仅在update和beforeUpdate钩子中可用。

3.2 全局与局部指令注册

全局指令(在 main.js 中注册)

import { createApp } from 'vue';
import App from './App.vue';const app = createApp(App);// 注册全局指令v-focus
app.directive('focus', {mounted(el) {el.focus(); // 元素挂载后自动聚焦}
});app.mount('#app');

局部指令(在组件中注册)

export default {directives: {focus: {mounted(el) {el.focus();}}}
};

3.3 参数、修饰符与动态绑定

1、 指令参数(Arg)

<div v-highlight:red>红色高亮</div>
<div v-highlight:blue>蓝色高亮</div>
app.directive('highlight', {mounted(el, binding) {// binding.arg获取参数值('red'或'blue')el.style.backgroundColor = binding.arg;}
});

2 修饰符(Modifiers)

通过点语法添加修饰符,改变指令默认行为:

<div v-hover-color.red.bold>带修饰符的悬停效果</div>
app.directive('hover-color', {mounted(el, binding) {// binding.modifiers获取修饰符对象 { red: true, bold: true }if (binding.modifiers.red) {el.style.color = 'red';}if (binding.modifiers.bold) {el.style.fontWeight = 'bold';}}
});

3 动态参数

使用中括号实现参数动态化:

<div v-bind:[(attributeName)]="value">动态绑定属性</div>
const attributeName=ref('title')

四、数字滚屏功能设计和代码实现解析

1、功能设计

在规定时间内按固定频率变化数字,使得从0到目标值等差递增并在页面显示当前值,此过程就形成数字滚动连续动画。

例如 :给定目标数值为10,动画时长为1000毫秒,从0开始显示,每隔100毫秒数值增加1,就能看到页面数字从0,1,2,3…10变化过程。

当递增值(等差值)不够1时,限制最低为1,并调整间隔时间,且每次显示数值取整数

2、代码实现解析

希望调用方式

方式1:

    <div v-numScrolling="20"></div>

直接传递目标数值,此方式动画时长采用默认值

方式2:

    <div v-numScrolling="{value:20,duration:2000}">0</div>

传递对象,其中value表示目标数值,duration表示动画时长

我们封装自定义指令希望同时支持2种传参形式


自定义指令内部实现解析

假设目标数值为targetValue,动画时长为duration, 每次数值变化间隔时间为interval,可计算出:

  • 总变化次数:
//变化次数
const count= Math.ceil(duration / interval);
  • 每次递增值(等差值):
//每次递增值
let n = targetValue / count; 

当n算出来小于1时(例如targetValue =10,duration=1000ms,interval=50ms),取整显示可能出现连续2次或多次数值一样情况造成动画不连贯,所以规定n大于等于1,此时重新计算间隔时间

if (n < 1) {//小于1默认每次累加1,重新计算间隔时间interval = duration / targetValue;n = 1;}

后续就可通过定时器递增数值显示:

let currentValue=0;//当前累加值(可能带小数)
let intervalId= setInterval(()=>{if(currentValue < targetValue){//通过Math.min限制超出最大值currentValue= Math.min(currentValue + n, targetValue);el.textContent = Math.round(currentValue);//取整显示到页面}else{//结束滚动clearInterval(intervalId)intervalId=null}
},interval )

五、完整代码

src/directive/numScrolling.js(自定义指令文件)

export function useNumScrollingDirective(app) {app.directive("numScrolling", {//挂载mounted(el, binding, vnode) {handleScrolling(binding, el, vnode);},//更新updated(el, binding, vnode) {let { targetValue, oldValue } = getValues(binding);//值有变才执行数字滚动if (targetValue !== oldValue) {handleScrolling(binding, el, vnode);}},});//获取目标数值、旧数值(上一次)\动画时长function getValues(binding) {let targetValue = 0; //最终显示的数字let oldValue = 0; //旧数字let param = binding.value; //入参let duration = 1500; //默认1500毫秒/**入参为对象类型*例如:{value:18,duration:5000}*/if (typeof param == "object") {targetValue = param.value;oldValue = binding?.oldValue?.value;duration = param.duration || 1500;}//入参为数字类型else if (typeof param == "number") {targetValue = param;oldValue = binding.oldValue;}//入参为字符串else if (typeof param == "string") {targetValue = Number(param);oldValue = Number(binding.oldValue);}return {targetValue,oldValue,duration,};}//数字滚动处理function handleScrolling(binding, el, vnode) {//数据挂载到当前节点对象,防止全局混用污染vnode.currentValue = 0; //当前显示的值vnode.intervalId = null; //定时器idlet { targetValue, duration } = getValues(binding); //targetValue:最终显示的数字vnode.duration = duration; //滚动时长,默认1500毫秒//0不滚动if (targetValue == 0) {el.textContent = 0;return;}if (vnode.intervalId !== null) {reset(); //重置数据}let interval = 50; //每次滚动(数字变化)间隔时间(毫秒)let count = Math.ceil(vnode.duration / interval); //变化次数let n = targetValue / count; //每次累加数值if (n < 1) {//小于1默认每次累加1,重新计算间隔时间interval = vnode.duration / targetValue;n = 1;}//定时改变数值vnode.intervalId = setInterval(() => {//未累加到目标值if (vnode.currentValue < targetValue) {vnode.currentValue = Math.min(vnode.currentValue + n, targetValue);el.textContent = Math.round(vnode.currentValue); //显示整数}//停止变化、重置数据else {reset(vnode);}}, interval);}/**** 清除定时器,重置数据*/function reset(vnode) {vnode.intervalId && clearInterval(vnode.intervalId);vnode.intervalId = null;vnode.currentValue = 0;}
}

说明:上述代码默认动画时长1500ms(支持自定义传参),默认变化间隔时间50ms,可以根据实际效果调整参数。需要注意的是updated周期函数内需要比较旧值和当前值是否变化才执行滚动动画。注意到我们把数据信息(定时器id,当前值、动画时长)存储到vnode(当前虚拟节点)对象上而非顶层全局变量,防止变量混用污染。

全局注册:
main.js

import {useNumScrollingDirective} from './directive/numScrolling.js'const app = createApp(App)
useNumScrollingDirective(app)
app.mount('#app')

页面调用:

<template><div><div v-numScrolling="11254">0</div><div v-numScrolling="2">0</div><div v-numScrolling="{ value: 18, duration: 3000 }">0</div><div v-numScrolling="{ value: 262, duration: 3000 }">0</div><div v-numScrolling="num">0</div></div>
</template>
<script setup>
import { ref } from "vue";
const num = ref(285);//4000ms后改变数值
setTimeout(() => {num.value = 17;
}, 4000);
</script>

运行结果:

请添加图片描述
(ps:由于视频转gif帧率变小造成看起滚动卡顿变慢,实际滚动效果丝滑快速)


总结

http://www.xdnf.cn/news/13262.html

相关文章:

  • 《Playwright:微软的自动化测试工具详解》
  • 联邦学习聚合参数操作详解
  • 关于个性化头像框设计的分享与服务说明
  • cv::Range的用法
  • AI时代的“数据之困”,什么是AI-Ready Data
  • 介绍一种直流过压保护电路
  • 蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
  • AUTOSAR图解==>AUTOSAR_TR_SWCModelingGuide
  • 【Java工程师面试全攻略】Day7:分布式系统设计面试精要
  • C++ 类继承
  • 《驭码CodeRider 2.0深度体验:AI驱动研发全流程革新,开发效率飙升300%!》
  • 实现建筑互联互通目标,楼宇自控系统在设备管理中作用凸显
  • 如何通过DNS解析实现负载均衡?有哪些优势?
  • DICOM批量修改工具
  • Pytest断言全解析:掌握测试验证的核心艺术
  • 15、企业固定资产(FA)全流程解析:从资产购置到资产处置
  • 产品经理入门到精通:01需求调研
  • 【Pandas】pandas DataFrame isna
  • 详解pytorch
  • 【学习笔记】虚函数+虚析构函数
  • 半导体设备基本通信标准介绍
  • shell脚本拔高习题
  • Word-- 制作论文三线表
  • SQL SERVER 数据库迁移的三种方法!
  • git clone 时报错超时的问题解决方案
  • 人工智能驱动的企业变革:从智能辅助到战略赋能
  • 【C#】C++的回调函数和C#的事件委托在某些方面有相似之处
  • 前端6月份之前的部分技术更新记录
  • mongDB
  • CentOS7.9 查询运维安全日志,排查恶意用户