微信小程序 | 动手实现双十一红包雨

📌个人主页个人主页
​🧀 推荐专栏小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏!从个人到商业的全套开发教程,实打实的干货分享,确定不来看看? 😻😻】
📝作者简介一个读过研、创过业,从全栈工程师转行做大数据开发的大厂人!
⭐️您的小小关注是我持续输出的动力!⭐️


干货内容推荐

🥇入门和进阶小程序开发,不可错误的精彩内容🥇 :



一、效果展示

在这里插入图片描述


二、应用场景

在众多的营销场景中,红包雨可以说是很百试不爽的套路了。对于红包雨,从表面来看就是简单地让用户点击并且给用户福利的形式。

但是,在设计红包雨的背后,前后端都可以深挖到很多的技术栈。

  • 前端:如何优化红包点击的特效体验?前端如何防止接口防刷?
  • 后端:如何设计高并发的红包雨?联动用户服务、优惠券服务、账户服务的架构如何设计?

在本文,先研究小程序端的红包雨特效以及用户的交互逻辑.

其中主要交互功能包括:

  • 红包雨动画生成
  • 红包雨点击事件
  • 红包雨的所需要满足的业务场景

三、项目技术分析

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://img-blog.csdnimg.cn/9a535df6885e49a597087f3db7145640.png">
    <img class="second" v-show="second==2" src="https://img-blog.csdnimg.cn/223a369d90744e06ac772eac1479f5cc.png">
      <img class="second" v-show="second==1" src="https://img-blog.csdnimg.cn/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://img-blog.csdnimg.cn/fe18340b8fbe4a4a80694b1b2feabc2a.png) no-repeat center;
        background-size: 100% 100%;
      }

      &.success {
        background: url(https://img-blog.csdnimg.cn/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>

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陶人超有料

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值