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

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

一、效果展示

微信截图_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>


相关文章
|
自然语言处理 搜索推荐 小程序
微信公众号接口:解锁公众号开发的无限可能
微信公众号接口是微信官方提供的API,支持开发者通过编程与公众号交互,实现自动回复、消息管理、用户管理和数据分析等功能。本文深入探讨接口的定义、类型、优势及应用场景,如智能客服、内容分发、电商闭环等,并介绍开发流程和工具,帮助运营者提升用户体验和效率。未来,随着微信生态的发展,公众号接口将带来更多机遇,如小程序融合、AI应用等。
|
12月前
|
消息中间件 人工智能 Java
抖音微信爆款小游戏大全:免费休闲/竞技/益智/PHP+Java全筏开源开发
本文基于2025年最新行业数据,深入解析抖音/微信爆款小游戏的开发逻辑,重点讲解PHP+Java双引擎架构实战,涵盖技术选型、架构设计、性能优化与开源生态,提供完整开源工具链,助力开发者从理论到落地打造高留存、高并发的小游戏产品。
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
4763 7
|
小程序 前端开发 测试技术
微信小程序的开发完整流程是什么?
微信小程序的开发完整流程是什么?
2687 7
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
5259 1
|
小程序 前端开发 Android开发
小程序微信分享功能如何开发?开放平台已绑定仍不能使用的问题?-优雅草卓伊凡
小程序微信分享功能如何开发?开放平台已绑定仍不能使用的问题?-优雅草卓伊凡
2721 29
小程序微信分享功能如何开发?开放平台已绑定仍不能使用的问题?-优雅草卓伊凡
|
小程序 JavaScript API
uni-halo + 微信小程序开发实录:我的第一个作品诞生记
这篇文章介绍了使用uni-halo框架进行微信小程序开发的过程,包括选择该框架的原因、开发目标以及项目配置和部署的步骤。
661 0
uni-halo + 微信小程序开发实录:我的第一个作品诞生记
|
人工智能 开发框架 机器人
AstrBot:轻松将大模型接入QQ、微信等消息平台,打造多功能AI聊天机器人的开发框架,附详细教程
AstrBot 是一个开源的多平台聊天机器人及开发框架,支持多种大语言模型和消息平台,具备多轮对话、语音转文字等功能。
9872 38
AstrBot:轻松将大模型接入QQ、微信等消息平台,打造多功能AI聊天机器人的开发框架,附详细教程
|
人工智能 自然语言处理 小程序
技术小白如何利用DeepSeek半小时开发微信小程序?
通过通义灵码的“AI程序员”功能,即使没有编程基础也能轻松创建小程序或网页。借助DeepSeek V3和R1满血版模型,用户只需用自然语言描述需求,就能自动生成代码并优化程序。例如,一个文科生仅通过描述需求就成功开发了一款记录日常活动的微信小程序。此外,通义灵码还提供智能问答模式,帮助用户解决开发中的各种问题,极大简化了开发流程,让普通人的开发体验更加顺畅。
4175 11
技术小白如何利用DeepSeek半小时开发微信小程序?
|
小程序 Java 关系型数据库
weixin163基于微信小程序的校园二手交易平台系统设计与开发ssm(文档+源码)_kaic
本文介绍了一款基于微信小程序的校园二手物品交易平台的开发与实现。该平台采用Java语言开发服务端,使用MySQL数据库进行数据存储,前端以微信小程序为载体,支持管理员和学生两种角色操作。管理员可管理用户、商品分类及信息、交易记录等,而学生则能注册登录、发布购买商品、参与交流论坛等。系统设计注重交互性和安全性,通过SSM框架优化开发流程,确保高效稳定运行,满足用户便捷交易的需求,推动校园资源共享与循环利用。