基于Vue的红包雨效果实现 您所在的位置:网站首页 微信vr红包 基于Vue的红包雨效果实现

基于Vue的红包雨效果实现

2024-01-11 21:34| 来源: 网络整理| 查看: 265

最近遇到了一个红包雨的需求,就大概这个样子:

image.png

具体效果可以进入拼多多查看。

在实现这个效果之前,我先安利一个我们老大之前写的基于requestAnimationFrame实现的小动画框架 《chito》。https://redmed.github.io/chito/

本文所述的需求均依赖此框架完成。

它可以根据你传入的相关动画参数来为你创建流畅的补间动画。

OK 回归正题。

首先思考几个问题。

1、红包要如何插入进来并展示掉落动画? 思考结果:平常的方式就是创建一个以单个红包为纬度的组件,我需要创建多少个红包,进页面就用v-for来创建多少个dom。但是我觉得以我司运营的套路,这个红包以后肯定会各种复用,我总不能谁复用这个功能都copy一遍我的代码过去吧 那简直太灾难了。 所以我准备将红包的插入方式改为函数调用的方式,就像在日常的后台管理系统中调用Element-UI中的 this.$message.success()一样,调用一次函数,红包就从上往下掉落一次,再根据不同的参数来单独控制每个红包的掉落速度、点击红包后对应的得分,甚至红包的颜色等等各项自定义属性。

2、红包的掉落需要哪些参数? 思考结果:以最最简单的交互来讲,可能只需要掉落速度,从何处掉落(红包掉落动画起始点的X轴),红包数量,红包雨持续的时间,点击红包后的反馈动画。 现在我有两个组件,1、父组件来负责红包的展示,以及红包的掉落的属性(上述那些)。2、子组件就是红包组件。

3、通过函数调用的组件如何编写和调用? 这个貌似官方没有什么对应的文档,我也是从Element-UI的源码中抄过来的。

4、未经允许直接复制本文的同学,我谢谢你。

OK进入开发,首先就是红包组件

首先我要定义一个红包容器,代码十分简单, 再给它一个简简单单的样式

* { user-select: none outline none } .box { width 0.592rem height 0.86rem position absolute top -1rem background url("https://coolcdn.igetcool.com/p/2021/1/f611d435ffb8c8c43c6983d807e27a65.png?_296x430.png") background-repeat no-repeat background-size contain transform rotate(0deg) }

这样,屏幕上就会出现一个红包,图是我随便找的一个。

image.png

红包应该是竖着的,我随便截了个图而已。

红包组件的详细代码,每一行都有注释,我不相信你看不懂😄

const {Animation, Clip} = require('chito') export default { name: "bonusRain", data() { return { // 代表红包该显示还是不显示 hidden: true, // 用于存放chito生成的动画实例 animation: null, // 用于存放从父组件传进来的配置项 options: null, // 用于记录红包是不是已经掉落 isDropped: false, // 用于判断红包在掉落的时候是顺时针旋转还是逆时针旋转 rotateComputed: 1 } }, methods: { // 点击事件 handleClick(e) { this.$nextTick(() => { // 因为每个红包都只能点击一次,所以点击红包后,就让动画停止 this.animation.stop() // 因为红包点击后也代表了销毁,所以在这里也要调用dropped事件 if (this.isDropped === false) { this.options.onDropped() this.isDropped = true } // 自定义的click事件,让事件能够分发出去,因为不是直接通过dom的方式向 // 父组件插入的组件,所以不能用$.emit分发事件 if (this.hidden === true) { this.options.onClick(e) } // 点击红包后修改红包样式 this.$refs['packet'].style.transform = 'rotate(0deg)' this.$refs['packet'].style.background = `url(https://coolcdn.igetcool.com/p/2021/1/c1a3568325f4fa97f843850aaa9713a7.jpg?_500x511.jpg)` this.$refs['packet'].style.backgroundSize = 'contain' this.$refs['packet'].style.backgroundRepeat = 'no-repeat' this.$refs['packet'].style.animation = 'unset' // 修改完样式总不能直接消失,所以适当给一个延时 setTimeout(() => { this.hidden = false }, 500) }) }, show(obj) { // 把传进来的配置项赋值给options this.options = obj // 自定义了一个beforShow的钩子,这样动画在初始化的时候想执行什么方法也方便 if(this.options.beforeShow){ this.options.beforeShow() } // 如果穿进来了一个封面图片,就替换掉当前的红包封面 if(obj.cover){ this.$refs['packet'].style.background = `url(${obj.cover})` this.$refs['packet'].style.backgroundSize = 'contain' this.$refs['packet'].style.backgroundRepeat = 'no-repeat' } // 创建一个动画剪辑 let clip = new Clip({ // 剪辑持续的时间,如果掉落距离是固定的,那么掉落时间就决定了红包掉落的速度 duration: obj.speed || 2000, // 剪辑重复一次,因为每个红包都是独立的 repeat: 1 }, { // 掉落的路线,从 -100开始到屏幕高度 y: [-100, document.documentElement.clientHeight] }) // 红包在掉落的过程中,每一次运动都会触发clip的update事件, // 如果你需要一些花里胡哨的效果,可以在这里定义 clip.on('update', (ev) => { var keyframe = ev.keyframe; // 因为要操作dom 所以需要用nextTick this.$nextTick(() => { // 掉落的过程中动态改变红包的y轴位置 this.$refs['packet'].style.top = keyframe.y + 'px'; // 根据配置项的x轴位置来设置红包的x轴位置 this.$refs['packet'].style.left = obj.xAxis + 'px' // 红包掉落的时候让它旋转起来 this.$refs['packet'].style.transform = `rotate(` + (ev.progress * 180 * this.rotateComputed) +`deg)` }) }); // 创建Animation实例 this.animation = new Animation(); // 把创建的剪辑添加到Animation实例中 this.animation.addClip([clip]); }, start(){ // 还在奇怪为什么我rotateComputed给了个数字1么? // 这里就告诉你,如果是1它就顺时针旋转,-1就是逆时针旋转 this.rotateComputed = (Math.random() * 10) > 5 ? 1:-1 // 让动画开始播放 this.animation.start() // 动画完成播放的事件 this.animation.on('complete', () => { // 动画结束之后回调onDropped事件 if (this.isDropped === false) { this.options.onDropped() // 动画完成后,把isDropped的值修改为是 // 代表红包已经掉落,理论上没什么用,但是玩意需要父组件做判断呢? this.isDropped = true } // 动画播放完成后,隐藏红包 this.hidden = false }) } } } * { user-select: none outline none } .box { width 0.592rem height 0.86rem position absolute top -1rem background url("https://coolcdn.igetcool.com/p/2021/1/f611d435ffb8c8c43c6983d807e27a65.png?_296x430.png") background-repeat no-repeat background-size contain transform rotate(0deg) }

因为要通过函数调用的方式调用组件,所以还需要写一个插件。

源码如下:

// 引入Vue import Vue from 'vue' // 引入红包组件 import bonusItem from './bonusItem.vue'; // 红包实例 let packet; // 组件挂载 function createItem(args) { // 用vue渲染红包组件并挂载 const vnode = new Vue({ render: h => h(bonusItem) }).$mount() // 将组件添加到body上 document.body.appendChild(vnode.$el) // 返回当前组件的实例 return vnode.$children[0] } export function showPacket(args) { // 创建组件 packet = createItem(args) // 将组件实例暴露出去 return packet } export default showPacket

这样,我们就可以通过调用函数的方式动态插入组件了

OK,接下来就是父组件的调用,完整代码如下:

分数{{point}}

游戏结束

开始游戏 // 引入红包组件 import showPacket from './bonusItem.js' export default { name: "test", data(){ return { // 倒计时 time: 10, // 红包数量 itemCount: 1, // 红包实例存储栈 dropStack: [], // 分数记录 point: 0, // 已经销毁的红包数量 dropped: 0, // 游戏状态 game:false, } }, // 进页面的时候,让页面高度固定为100vh并且不能滚动 beforeCreate() { document.body.style.maxHeight = '100vh' document.body.style.overflow = 'hidden' }, // 离开页面之前 恢复,避免不影响其它页面 beforeDestroy() { document.body.style.maxHeight = 'unset' document.body.style.overflow = 'unset' }, mounted() { // 创建红包DOM,并把每次函数执行返回的dom实例放到存储栈中 let arr = [] for(let i in this.itemStack){ let instance = showPacket() arr.push(instance) instance.show({ // 遍历x轴,因为x轴是随机生成的,所以红包掉落的起始位置也是随机的 xAxis: this.itemStack[i], // 红包掉落的速度 speed: 3000, // 红包点击事件 onClick: () =>{ // 每次点击,分数+1 this.point++ }, // 红包销毁事件 onDropped: () => { // 销毁数量加1 ++this.dropped // 如果销毁数量等于红包数量,那么游戏停止 if(this.dropped === this.itemCount){ this.game = true } } }) } // 将创建的红包实例存入栈中 this.dropStack = arr }, methods: { // 红包雨开始 start(){ this.game = false; // 遍历红包栈,并根据事件平均分配掉落的时机 for(let i in this.dropStack){ setTimeout(() => { this.dropStack[i].start() // 假设红包雨持续的事件是10秒,红包数量为20个,那么每个红包掉落的时机就是 // 当前遍历的索引 * (10秒 * 1000毫秒 / 红包数量) // 等于每 i * 10000毫秒 / 20 掉落一个。 // 这里不懂的可以问我 }, i * (this.time * 1000 / this.itemCount)) } }, }, computed: { // 按照红包数量生成对应的X轴随机数 itemStack(){ let arr = [] for(let i = 0; i < this.itemCount; i++){ // 保证红包的x轴在固定的范围内,根据实际需求控制 arr.push(Math.floor(Math.random() * ( 300 - 20) + 20)) } return arr } } } .container { background-size contain background-repeat no-repeat height 100vh overflow hidden color #000 font-size 0.2rem }

最终的效果如下:

image.png

因为没有工具录制gif 所以截了个示例图,快去自己试试吧。

搬运本文,请注明原文地址,谢谢。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有