一、效果展示
二、应用场景
在众多的营销场景中,红包雨可以说是很百试不爽的套路了。对于红包雨,从表面来看就是简单地让用户点击并且给用户福利的形式。
但是,在设计红包雨的背后,前后端都可以深挖到很多的技术栈。
- 前端:如何优化红包点击的特效体验?前端如何防止接口防刷?
- 后端:如何设计高并发的红包雨?联动用户服务、优惠券服务、账户服务的架构如何设计?
在本文,先研究小程序端的红包雨特效以及用户的交互逻辑
.
其中主要交互功能包括:
- 红包雨动画生成
- 红包雨点击事件
- 红包雨的所需要满足的业务场景
三、项目技术分析
3.1 技术栈基础必备
- (1)css动画属性
transform
属性的使用【控制红包进行角度旋转】:
值 | 描述 |
rotate(angle) | 定义 2D 旋转,在参数中规定角度。 |
rotate3d(x,y,z,angle) | 定义 3D 旋转。 |
rotateX(angle) | 定义沿着 X 轴的 3D 旋转。 |
rotateY(angle) | 定义沿着 Y 轴的 3D 旋转。 |
rotateZ(angle) | 定义沿着 Z 轴的 3D 旋转。 |
动画animation属性的使用【控制红包】:
推荐学习:通过示例深入学习animation属性
(2)JavaScript定时器的使用
setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
setTimeout() :在指定的毫秒数后调用函数或计算表达式。
3.2 技术关键点剖析
- (1)红包掉落动画随机生成
先使用js中的Math.random
生成红包随机旋转的角度,使得红包掉落的效果更加自然。再使用定时器功能使得红包动画循环播放。
startRedPacket() { let win = 0 // 获取屏幕宽度 uni.getSystemInfo({ success: function(res) { win = res.windowWidth } }); // let win = document.documentElement.clientWidth || document.body.clientWidth let rotate = (parseInt(Math.random() * 90 - 45)) + "deg" // 旋转角度 let w = (Math.random() * 90) + 120 let durTime = parseInt(Math.random() * 1.5) + 2.5 + 's' // 时间 let left = parseInt(Math.random() * win) if (left < 0) { left = 0 } else if (left > (win - w)) { left = (win + 65) } this.liParams.push({ left: left + 'rpx', width: w + 'rpx', transforms: 'rotate(' + rotate + ')', durTime: durTime, status: 0 // 0 默认 1 中奖 2 未中奖 }) setTimeout(() => { // 多少时间结束 clearTimeout(this.timer) return false }, this.duration * 1000) // 红包密度 this.timer = setTimeout(() => { this.startRedPacket() }, 300) },
- (2)红包中奖业务的控制
红包雨的功能主要在于达到促销功能,在业务的要求下,要实现红包雨中的红包不是每个都有大奖,也不是没有奖,这就需要我们按照特定的业务需求去设置红包的中奖率,以及设计相应的开奖动画。
if (this.selectedNum >= 3 && item.status == 0) { item.status = 2 // return false } if (this.clickNum == this.randomNum) { if (item.status == 0 && this.selectedNum < 3) { this.randomNum = Math.ceil(Math.random() * 6) this.clickNum = 0 this.selectedNum++ item.status = 1 // 随机出现只中奖 this.acquisitionNum++ } } else { item.status = 2 }
代码中通过status
属性实现红包中奖状态的控制:
参数值 | 中奖情况 |
0 | 空红包 |
1 | 未中奖 |
2 | 已中奖 |
四、完整源码
<template> <view class="rainBox"> <view class="countDown" v-if="secondMask"> <img class="second" v-show="second==3" src="https://ucc.alicdn.com/images/user-upload-01/9a535df6885e49a597087f3db7145640.png"> <img class="second" v-show="second==2" src="https://ucc.alicdn.com/images/user-upload-01/223a369d90744e06ac772eac1479f5cc.png"> <img class="second" v-show="second==1" src="https://ucc.alicdn.com/images/user-upload-01/a2a5909e79314903855b3774bad7ec11.png"> </view> <view v-if="!secondMask"> <view class="redNum"> <text class="icon">{{duration}}s</text> <text class="winnum">当前抢到红包:<text class="redmoney">{{acquisitionNum}}</text>元</text> </view> <view class="red_packet" id="red_packet"> <view v-for="(item, index) in liParams"> <view class="package" :style="{ left: item.left,width:item.width, height:item.width, animationDuration: item.durTime, webkitAnimationDuration: item.durTime}" :data-index="index" @webkitAnimationEnd="removeDom" @click="tap(item)"> <text :style="{ width:item.width, height:item.width,transform: item.transforms, webkitTransform: item.transforms}" :class="[{ 'defaul':item.status==0},{'success':item.status==1},{'fail':item.status==2}]"></text> </view> </view> <view v-if="show1==true" @click="show = false"> <view class="warp flexcenter"> <view class="rect" @tap.stop :style="{background: 'url(https://ctyh88.oss-cn-shenzhen.aliyuncs.com/static/puzzle/tanchuhongbao.png)no-repeat center center / 100%'}"> <view class="rectcenter"> <view class="rt-money">{{acquisitionNum}}元</view> <view class="rt-money1">已存余额</view> </view> <view class="btnredbg" @click="show1 = false">我知道了</view> <view class="iconfil"> <u-icon name="close-circle-fill" color="#b39b8f" size="40" class="iconfil" @click="show1 = false"></u-icon> </view> </view> </view> </view> </view> </view> </view> </template> <script> export default { // components: { noPrize }, data() { return { second: 3, // 倒计时 secondMask: true, //倒计时弹层 liParams: [], // 红包数组 timer: null, duration: 10, // 持续时间 selectedNum: 0, // 选中红包个数,不超过3个 clickNum: 0, // 点击的次数 randomNum: Math.ceil(Math.random() * 6), // 1~6 随机数 couponArr: [], acquisitionNum: 0, // 获得红包个数 show1: false, // 获取红包弹窗 } }, created() { this.countDownFn() }, methods: { // 5秒倒计时 countDownFn() { let self = this let timer = setInterval(() => { if (self.second == 0) { self.secondMask = false clearInterval(timer) self.startRedPacket() self.countDownFn20() } else { self.second-- } }, 1000) }, // 20秒倒计时 countDownFn20() { let self = this let timer = setInterval(() => { if (self.duration == 0) { clearInterval(timer) this.show1 = true console.log('结束') } else { self.duration-- } }, 1000) }, tap(item) { this.clickNum++ if (this.selectedNum >= 3 && item.status == 0) { item.status = 2 // return false } if (this.clickNum == this.randomNum) { if (item.status == 0 && this.selectedNum < 3) { this.randomNum = Math.ceil(Math.random() * 6) this.clickNum = 0 this.selectedNum++ item.status = 1 // 随机出现只中奖 this.acquisitionNum++ } } else { item.status = 2 } }, startRedPacket() { let win = 0 // 获取屏幕宽度 uni.getSystemInfo({ success: function(res) { win = res.windowWidth } }); let rotate = (parseInt(Math.random() * 90 - 45)) + "deg" // 旋转角度 let w = (Math.random() * 90) + 120 let durTime = parseInt(Math.random() * 1.5) + 2.5 + 's' // 时间 let left = parseInt(Math.random() * win) if (left < 0) { left = 0 } else if (left > (win - w)) { left = (win + 65) } this.liParams.push({ left: left + 'rpx', width: w + 'rpx', transforms: 'rotate(' + rotate + ')', durTime: durTime, status: 0 // 0 默认 1 中奖 2 未中奖 }) setTimeout(() => { // 多少时间结束 clearTimeout(this.timer) return false }, this.duration * 1000) // 红包密度 this.timer = setTimeout(() => { this.startRedPacket() }, 300) }, removeDom(e) { this.package1=true this.package=false // let target = e.currentTarget // document.querySelector('#red_packet').removeChild(target) } } } </script> <style lang="less" scoped> // 红包弹窗 .warp { width: 100%; height: 100vh; overflow: hidden; .rect { width: 672rpx; height: 840rpx; margin: auto; .rectcenter { height: 554rpx; text-align: center; .rt-money { font-size: 65rpx; font-weight: 800; padding: 80rpx 0; color: rgba(238, 195, 24, 1); } .rt-money1 { font-size: 30rpx; font-family: PingFang SC; font-weight: 500; color: rgba(238, 195, 24, 1); padding: 20rpx 0; } } .btnredbg { width:370rpx; height:80rpx; background:rgba(255,211,125,1); border-radius:40rpx; line-height: 80rpx; margin: auto; color: #F42335; font-size: 32rpx; text-align: center; } .iconfil{ width:77rpx; margin: 60rpx auto; } } } .rainBox { margin: 0; padding: 0; position: relative; width: 100%; height: 92vh; overflow: hidden; background: url(https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.888ppt.com%2F%2FUpload%2Fthumb%2F20191014%2F781a066454151825.jpg%21h400&refer=http%3A%2F%2Fpic.888ppt.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1670587612&t=6f6843e0025442cf321946c480f82cbc) no-repeat center; background-size: 100%; .winnum { display: inline-block; color: #fff; font-size: 40rpx; padding: 60rpx; .redmoney { color: #FED134; font-weight: bold; padding-right: 10rpx; } } .icon { display: inline-block; width: 82rpx; height: 82rpx; background: #6C0A75; font-size: 60rpx; line-height: 92rpx; text-align: center; position: absolute; top: 40rpx; right: 40rpx; z-index: 4; background: url(https://img.51fanbei.com/h5/app/activity/redRain_08.png) no-repeat center; background-size: 100% 100%; color: #eec318; font-weight: bolder; i { font-size: 24rpx; } } } .countDown { width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 2; overflow: hidden; background: url(https://img.51fanbei.com/h5/app/activity/redRain_02.png) no-repeat center; background-size: 100% 100%; .second { width: 340rpx; height: 394rpx; margin: 410rpx auto; display: block; // margin-top: 205px; } } .red_packet { text { width: 250rpx; height: 250rpx; display: block; &.defaul { background: url(https://img1.baidu.com/it/u=936677898,247021280&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=749) no-repeat center; background-size: 100% 100%; } &.fail { background: url(https://ucc.alicdn.com/images/user-upload-01/fe18340b8fbe4a4a80694b1b2feabc2a.png) no-repeat center; background-size: 100% 100%; } &.success { background: url(https://ucc.alicdn.com/images/user-upload-01/1e957a43234f4175bc537fb9770de5ea.png) no-repeat center; background-size: 100% 100%; } } .package1 { display: none; } .package { position: absolute; animation: all 3s linear; top: -200rpx; z-index: 3; animation: aim_move 5s linear 1 forwards; // &.package { // } } a { display: block; } } @keyframes aim_move { 0% { transform: translateY(0); } 100% { transform: translateY(120vh); } } </style>