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

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

一、效果展示

微信截图_20221124132033.png


二、应用场景

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


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

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


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

其中主要交互功能包括:

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


三、项目技术分析

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>


相关文章
|
2月前
|
小程序 JavaScript Java
微信小程序的后端开发需要使用什么语言?
【8月更文挑战第22天】微信小程序的后端开发需要使用什么语言?
312 65
ly~
|
7天前
|
存储 供应链 小程序
除了微信小程序,PHP 还可以用于开发哪些类型的小程序?
除了微信小程序,PHP 还可用于开发多种类型的小程序,包括支付宝小程序、百度智能小程序、抖音小程序、企业内部小程序及行业特定小程序。在电商、生活服务、资讯、工具、娱乐、营销等领域,PHP 能有效管理商品信息、订单处理、支付接口、内容抓取、复杂计算、游戏数据、活动规则等多种业务。同时,在企业内部,PHP 可提升工作效率,实现审批流程、文件共享、生产计划等功能;在医疗和教育等行业,PHP 能管理患者信息、在线问诊、课程资源、成绩查询等重要数据。
ly~
40 6
|
7天前
|
小程序 JavaScript API
微信小程序开发学习之页面导航(声明式导航和编程式导航)
这篇文章介绍了微信小程序中页面导航的两种方式:声明式导航和编程式导航,包括如何导航到tabBar页面、非tabBar页面、后退导航,以及如何在导航过程中传递参数和获取传递的参数。
微信小程序开发学习之页面导航(声明式导航和编程式导航)
|
2月前
|
小程序 JavaScript
Taro@3.x+Vue@3.x+TS开发微信小程序,使用轮播图
本文介绍了使用 Taro 和 Vue 创建轮播组件的两种方法:一是通过 `&lt;swiper&gt;` 实现,二是利用 Nut UI 的 `&lt;nut-swiper&gt;` 组件实现。
Taro@3.x+Vue@3.x+TS开发微信小程序,使用轮播图
|
21天前
|
存储 移动开发 监控
微信支付开发避坑指南
【9月更文挑战第11天】在进行微信支付开发时,需遵循官方文档,确保权限和参数配置正确。开发中应注重安全,验证用户输入,合理安排接口调用顺序,并处理异常。上线后需实时监控支付状态,定期检查配置,关注安全更新,确保系统稳定运行。
|
27天前
|
移动开发 小程序 JavaScript
uni-app开发微信小程序
本文详细介绍如何使用 uni-app 开发微信小程序,涵盖需求分析、架构思路及实施方案。主要功能包括用户登录、商品列表展示、商品详情、购物车及订单管理。技术栈采用 uni-app、uView UI 和 RESTful API。文章通过具体示例代码展示了从初始化项目、配置全局样式到实现各页面组件及 API 接口的全过程,并提供了完整的文件结构和配置文件示例。此外,还介绍了微信授权登录及后端接口模拟方法,确保项目的稳定性和安全性。通过本教程,读者可快速掌握使用 uni-app 开发微信小程序的方法。
57 3
|
2月前
|
小程序
Taro@3.x+Vue@3.x+TS开发微信小程序,设置转发分享
本文介绍了Taro中`useShareAppMessage`的使用方法,需在页面配置`enableShareAppMessage: true`并重新编译。
Taro@3.x+Vue@3.x+TS开发微信小程序,设置转发分享
|
2月前
|
小程序 数据安全/隐私保护
Taro@3.x+Vue@3.x+TS开发微信小程序,网络请求封装
在 `src/http` 目录下创建 `request.ts` 文件,并配置 Taro 的网络请求方法 `Taro.request`,支持多种 HTTP 方法并处理数据加密。
Taro@3.x+Vue@3.x+TS开发微信小程序,网络请求封装
|
2月前
|
小程序
Taro@3.x+Vue@3.x+TS开发微信小程序,上传文件
本文介绍如何在Taro项目中使用Nut UI的`&lt;nut-uploader/&gt;`组件实现图片上传功能,并通过示例代码展示了自定义上传逻辑的方法。
Taro@3.x+Vue@3.x+TS开发微信小程序,上传文件
|
2月前
|
小程序
Taro@3.x+Vue@3.x+TS开发微信小程序,根据系统主题展示不同样式(darkMode)
本文介绍如何在Taro项目中配置深色模式。通过在`src/app.config.ts`设置`darkmode`选项和在`theme.json`中定义主题变量,可以实现跟随系统主题的界面风格切换。
Taro@3.x+Vue@3.x+TS开发微信小程序,根据系统主题展示不同样式(darkMode)

热门文章

最新文章

下一篇
无影云桌面