一、原型依据
在我这个项目中小程序端所需要实现的只有红包雨的下落动画和通屏背景图的兼容,关于红包点击金额的计算是由后端实现的。首先来看下需要实现的效果图。
二、实现代码
首先是第一次进入的页面,在这个页面的时候会进行静默登录。静默登录成功的话会显示当前剩余次数,否则在点击开始的时候回跳转登录页面。不论是否登录成功都会去调用活动信息监测的接口,判断当前是否有可以参加的活动。在点击开始后进行三秒钟的倒计时,倒计时结束则转入下一个界面实现红包雨的下落。
<!--RedEnvelopes/pages/RedEnvelopes/index/index.wxml-->
<nav-bar navbar-data='{
{nvabarData}}'></nav-bar>
<view style='padding-top: {
{
height}}px;' wx:if="{
{ready}}">
<block wx:if="{
{!showTime}}">
<view class="start" bindtap="participateIn" wx:if="{
{readyTime != 0}}">
<view class="start-aperture">
<view class="start-aperture-bg">
<text class="start-aperture-txt" wx:if="{
{!showTime}}">开始</text>
<text class="start-aperture-txt" wx:else>{
{readyTime}}</text>
</view>
</view>
</view>
</block>
<block wx:else>
<view class="start" wx:if="{
{readyTime != 0}}">
<view class="start-aperture">
<view class="start-aperture-bg">
<text class="start-aperture-txt" wx:if="{
{!showTime}}">开始</text>
<text class="start-aperture-txt" wx:else>{
{readyTime}}</text>
</view>
</view>
</view>
</block>
<view class="remaTimes" wx:if="{
{showLogin && !showTime}}">今日剩余次数: {
{RemainingTimes}}/{
{MaxSharingAwardTimes}}</view>
<view class="rule" wx:if="{
{!showTime}}">
<view class="rule-top">
<text class="iconfont iconxiangxia2"></text>
</view>
<view class="rule-body">
<view class="rule-body-one">
<view class="rule-body-title">规则:</view>
<view class="rule-body-html">{
{ActivityRules}}</view>
</view>
</view>
</view>
</view>
<!-- 红包雨组件 -->
<block wx:if="{
{readyTime == 0}}">
<sol-packet-rain visible="{
{visible}}" createSpeed="{
{createSpeed}}" time="{
{time}}" min="{
{min}}" max="{
{max}}" bind:finish="success"></sol-packet-rain>
</block>
// RedEnvelopes/pages/RedEnvelopes/index/index.js
const api = require('../../../../server/api.js');
const http = require('../../../../server/request.js');
const appJs = require('../../../../utils/uselogn.js');
const app = getApp()
let readyTimer = null
Page({
/**
* 页面的初始数据
*/
data: {
// 导航头组件所需的参数
nvabarData: {
showCapsule: 1, //是否显示左上角图标 1表示显示 0表示不显示
title: '抢红包', //导航栏 中间的标题
white: true, // 是就显示白的,不是就显示黑的。
address: api.pictureServer + '/res/shopImg/RedEnvelopes.png' // 加个背景 不加就是没有
},
// 导航头的高度
height: app.globalData.height * 2 + 20,
readyTime: 3,
showTime: false,
btnText: '获取验证码',
phone: '',
VerificationCode: '',
unbind: false,
showLogin: true,
ready: false,
tell: '',
redId: '', //抢红包活动标识
BackgroundMap: '', //活动背景图
ThemeMap: '', //活动主题图
ActivityRules: '', //活动规则
ShareDescription: '', //分享描述
ShareIcon: '', //分享图标
MaxSharingAwardTimes: '0',
RemainingTimes: '0'
},
onLogin: appJs.userLogin,
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
if (options.refer && options.ShareMemberId) {
let SharedMemberId = wx.getStorageSync('CustomerService').MemberId;
let parm = {
Id: options.refer,
ShareMemberId: options.ShareMemberId,
SharedMemberId: SharedMemberId
}
this.getRedEnvelopeLotteryShare(parm)
let white = 'nvabarData.white'
this.setData({
[white]: false
})
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
this.setData({
ready: true,
showTime: false,
readyTime: 3
})
this.onLogin(this.authCallback, this.authCallback1);
},
authCallback() {
let that = this
// 获取当前时间抢红包信息
that.getGrabRed();
that.setData({
showLogin: true
})
},
authCallback1() {
let that = this
// 获取当前时间抢红包信息
that.getGrabRed();
that.setData({
showLogin: false
})
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
},
// 开始准备倒计时
cultdown: function() {
let _this = this
let {
readyTime
} = this.data
readyTimer = setInterval(function() {
if (--readyTime <= 0) {
clearInterval(readyTimer)
// 显示红包雨
_this.run();
}
_this.setData({
readyTime: readyTime
})
}, 1000)
},
start: function() {
if (!this.data.showLogin) {
setTimeout(() => {
wx.navigateTo({
url: '/pages/member/loginAndRegister/loginAndRegister',
})
}, 500)
return
}
if (this.data.RemainingTimes == 0){
return
}
if (!this.data.showTime) {
this.cultdown();
this.setData({
showTime: true
})
}
},
run: function() {
let address = 'nvabarData.address'
this.setData({
visible: true,
createSpeed: 5, // 速度
time: 10, // 游戏时间
min: 1, // 金币最小是0
max: 1, // 金币最大是10
[address]: this.data.ThemeMap || api.pictureServer + '/res/shopImg/RedEnvelopes1.png'
})
clearInterval(this.readyTimer);
},
// 结束
success(e) {
let that = this;
let redId = this.data.redId;
console.log('bind:finish', e.detail)
let RedEnvelopeNum = e.detail;
// 中奖
let parm = {
Id: redId,
RedEnvelopeNum: RedEnvelopeNum
}
http.requestLoading('/api/services/app/RedEnvelope/ExcuteGrabRedEnvelope', parm, '', 'POST').then(res => {
if (res.data.Result.Code === 0) {
console.log(res)
that.setData({
visible: false // 隐藏界面
})
wx.navigateTo({
url: '/RedEnvelopes/pages/RedEnvelopes/WinningRedEnvelope/WinningRedEnvelope?Id=' + res.data.Result.Data.Id + '&PartId=' + res.data.Result.Data.PartId + '&ShareDescription=' + that.data.ShareDescription + '&ShareIcon=' + that.data.ShareIcon
})
}
})
},
// 立即参与
participateIn() {
let that = this;
let redId = this.data.redId;
let parm = {
Id: redId
}
http.requestLoading('/api/services/app/RedEnvelope/GetGrabRedEnvelopeAppSingle', parm, '', 'GET').then(res => {
if (res.data.Result.Code === 0) {
console.log(res)
if (redId != res.data.Result.Data.Id) {
wx.showToast({
title: '当前活动信息不符',
icon: 'none'
})
} else {
that.setData({
redId: res.data.Result.Data.Id
})
// 验证会员是否可以参加该活动
let parms = {
Id: res.data.Result.Data.Id,
RedEnvelopeNum: 0
}
if (!this.data.showLogin) {
setTimeout(() => {
wx.navigateTo({
url: '/pages/member/loginAndRegister/loginAndRegister',
})
}, 500)
return
}
http.requestLoading('/api/services/app/RedEnvelope/DrawGrabRedEnvelope', parms, '', 'POST').then(res => {
if (res.data.Result.Code === 0) {
that.setData({
ready: true
})
that.start();
}
})
}
}
})
},
// 获取当前时间抢红包信息
getGrabRed: function(redId) {
let that = this;
let address = 'nvabarData.address'
http.requestLoading('/api/services/app/RedEnvelope/GetGrabRedEnvelopeAppSingle', {
}, '', 'GET').then(res => {
if (res.data.Result.Code === 0) {
console.log(res)
that.setData({
redId: res.data.Result.Data.Id,
BackgroundMap: res.data.Result.Data.BackgroundMap,
ThemeMap: res.data.Result.Data.ThemeMap, //活动主题图
ActivityRules: res.data.Result.Data.ActivityRules, //活动规则
ShareDescription: res.data.Result.Data.ShareDescription, //分享描述
ShareIcon: res.data.Result.Data.ShareIcon, //分享图标
[address]: res.data.Result.Data.BackgroundMap
})
if (that.data.showLogin) {
that.getRemainingTimes();
}
}
})
},
// 分享得额外抽奖次数
getRedEnvelopeLotteryShare(parm) {
http.requestLoading('/api/services/app/RedEnvelope/RedEnvelopeLotteryShare', parm, '', 'POST').then(res => {
if (res.data.Result.Code === 0) {
console.log(res)
}
})
},
// 获取总次数
getRemainingTimes() {
let that = this;
let parm = {
Id: this.data.redId
}
http.requestLoading('/api/services/app/RedEnvelope/GetRemainingTimes', parm, '', 'GET').then(res => {
if (res.data.Result.Code === 0) {
console.log(res)
that.setData({
MaxSharingAwardTimes: res.data.Result.Data.MaxSharingAwardTimes,
RemainingTimes: res.data.Result.Data.RemainingTimes
})
}
})
}
})
/* RedEnvelopes/pages/RedEnvelopes/index/index.wxss */
page {
position: relative;
}
.login {
width: 626rpx;
height: 554rpx;
background: #fff;
border-radius: 10rpx;
position: absolute;
top: 50%;
left: 0;
right: 0;
margin: auto;
}
.login-center {
width: 501rpx;
height: 138rpx;
border-bottom: 1rpx solid #e2e2e2;
margin: auto;
display: flex;
align-items: center;
}
.userIpt {
font-size: 28rpx;
font-family: Source Han Sans CN Regular, Source Han Sans CN Regular-Regular;
font-weight: 400;
color: #999;
}
.posion {
position: relative;
}
.zc_view {
position: absolute;
right: 0;
top: 40rpx;
width: 170rpx;
height: 52rpx;
line-height: 52rpx;
margin-left: 12rpx;
border: 1rpx solid #e2e2e2;
text-align: center;
font-size: 24rpx;
border-radius: 10rpx;
}
.login-state {
font-size: 40rpx;
font-family: Microsoft YaHei Regular, Microsoft YaHei Regular-Regular;
font-weight: 400;
color: #333;
text-align: center;
margin-top: 146rpx;
}
.login-ready {
font-size: 30rpx;
font-family: Microsoft YaHei Regular, Microsoft YaHei Regular-Regular;
font-weight: 400;
color: #333;
text-align: center;
margin-top: 89rpx;
}
.login-btn {
width: 299rpx;
height: 74rpx;
background: #fc7297;
border-radius: 37rpx;
box-shadow: 0rpx 2rpx 6rpx 0rpx rgba(3, 0, 6, 0.1);
margin: auto;
margin-top: 65rpx;
display: flex;
align-items: center;
justify-content: center;
}
.btn-txt {
font-size: 30rpx;
font-family: Microsoft YaHei Regular, Microsoft YaHei Regular-Regular;
font-weight: 500;
color: #fff;
}
/* 开始按钮 */
.start {
position: absolute;
top: 45%;
padding: 0 250rpx;
}
.start-aperture {
width: 250rpx;
height: 250rpx;
background: rgb(247, 136, 44, 0.29);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.start-aperture-bg {
width: 210rpx;
height: 210rpx;
background: linear-gradient(0deg, #f0510b 0%, #ffc350 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.start-aperture-txt {
font-size: 60rpx;
font-family: Source Han Sans CN Medium, Source Han Sans CN Medium-Medium;
font-weight: 500;
color: #fff;
}
/* 规则 */
.rule {
position: absolute;
left: 0;
right: 0;
top: 73%;
}
.rule-top {
width: 160rpx;
height: 36rpx;
background: rgb(237, 216, 255, 0.4);
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
margin: auto;
text-align: center;
}
.iconxiangxia2 {
font-size: 28rpx;
color: #fff;
}
.rule-body {
width: 660rpx;
/* height: 208rpx; */
background: rgb(237, 216, 255, 0.4);
border-radius: 20rpx;
margin: auto;
padding: 38rpx 0 38rpx 38rpx;
}
.rule-body-one{
display: flex;
}
.rule-body-title {
display: inline-block;
width: 81rpx;
font-size: 26rpx;
font-family: Source Han Sans CN Normal, Source Han Sans CN Normal-Normal;
color: #fff;
}
.rule-body-html {
display: inline-block;
width: 417rpx;
font-size: 26rpx;
font-family: Source Han Sans CN Normal, Source Han Sans CN Normal-Normal;
color: #fff;
}
/* 新 */
.remaTimes{
position: absolute;
left: 0;
right: 0;
top: 65%;
font-size: 30rpx;
font-family: Microsoft YaHei Regular, Microsoft YaHei Regular-Regular;
font-weight: 400;
color: #fff;
text-align: center;
margin-top: 20rpx;
}
在当前的页面中引入了两个组件,第一个是实现自定义导航栏的,因为在当前的界面中背景图的要求是全屏的。第二个组件就是红包雨的下落是参考网上一大佬的红包雨实现的。
//app.js
// 引入请求文件
App({
onLaunch: function(e) {
//获取设备顶部窗口的高度(不同设备窗口高度不一样,根据这个来设置自定义导航栏的高度)
wx.getSystemInfo({
success: res => {
this.globalData.height = res.statusBarHeight
}
})
this.getSystemInfo();
},
globalData: {
share: false, // 分享默认为false
height: 0, // 顶部高度
systemInfo: {
} //设备信息
},
getSystemInfo: function() {
var info = util.getSystemInfoSync();
var iphoneX = "";
if (info) {
var statusBarHeight = info.statusBarHeight;
var model = info.model;
var windowHeight = info.windowHeight;
var totalTopHeight = 68;
model.indexOf("iPhone X") !== -1 ?
((totalTopHeight = 94), (iphoneX = "iphone-x")) :
-1 !== model.indexOf("iPhone") ? (totalTopHeight = 64) : -1 !== model.indexOf("MI 8") && (totalTopHeight = 88);
var titleBarHeight = totalTopHeight - statusBarHeight;
this.globalData.systemInfo = Object.assign({
}, info, {
statusBarHeight,
titleBarHeight,
totalTopHeight,
iphoneX,
windowHeight
})
}
}
})
在app.js
中针对不同的设备的导航条的高度进行了处理,使之能够实现适配。
<!--componets/shoppingMall-components/navbar/navbar.wxml-->
<view class='nav-wrap' style='height: {
{
height*2 + 20}}px;'>
<!-- 导航栏背景图片 -->
<image class="backgroundimg" src="{
{navbarData.address}}" bindload="imgLoaded" style="width:{
{
imageWidth}}px;height:{
{
imageHeight}}px" />
<!-- // 导航栏 中间的标题 -->
<view class='nav-title' wx:if='{
{!navbarData.white}}' style='line-height: {
{
height*2 + 44}}px;'>
{
{navbarData.title}}
</view>
<view class='nav-title' wx:else='{
{!navbarData.white}}' style='line-height: {
{
height*2 + 44}}px; color:#ffffff'>
{
{navbarData.title}}
</view>
<view style='display: flex; justify-content: space-around;flex-direction: column'>
<!-- // 导航栏 左上角的返回按钮 -->
<!-- // 其中wx:if='{
{navbarData.showCapsule}}' 是控制左上角按钮的显示隐藏,首页不显示 -->
<view class='nav-capsule' style='height: {
{
height*2 + 44}}px;' wx:if='{
{navbarData.showCapsule}}'>
<!-- //左上角的返回按钮,wx:if='{
{!share}}'空制返回按钮显示 -->
<!-- //从分享进入小程序时 返回上一级按钮不应该存在 -->
<!-- navbarData.white是控制按钮颜色的,因为背景有深浅色,返回按钮自己找图片 -->
<view bindtap='_navback' wx:if='{
{showBack}}'>
<text class="iconfont iconxiangzuo"></text>
</view>
<view bindtap='_navbackTo' wx:else>
<text class="iconfont iconxiaochengxushouye"></text>
</view>
</view>
</view>
</view>
<!-- 导航栏下面的背景图片 -->
<image class="backgroundimg" src="{
{navbarData.address}}" bindload="imgLoaded" style="width:{
{
imageWidth}}px;height:{
{
imageHeight}}px" />
// componets/shoppingMall-components/navbar/navbar.js
const app = getApp()
const util = require('../../../utils/indexPage.js');
Component({
/**
* 组件的属性列表
*/
properties: {
navbarData: {
//navbarData 由父页面传递的数据,变量名字自命名
type: Object,
value: {
},
observer: function(newVal, oldVal) {
}
}
},
options: {
styleIsolation: 'apply-shared'
},
/**
* 组件的初始数据
*/
data: {
height: '',
//默认值 默认显示左上角
navbarData: {
showCapsule: 1
},
imageWidth: wx.getSystemInfoSync().windowWidth, // 背景图片的高度
imageHeight: '', // 背景图片的长度,通过计算获取
showBack:false
},
attached: function() {
// 获取是否是通过分享进入的小程序
this.setData({
share: app.globalData.share
})
let pages = getCurrentPages();
if (pages.length > 1){
this.setData({
showBack: true
})
}
// 定义导航栏的高度 方便对齐
this.setData({
height: app.globalData.height
})
},
/**
* 组件的方法列表
*/
methods: {
// 返回上一页面
_navback() {
wx.navigateBack()
},
_navbackTo(){
util.indexPage();
},
// 计算图片高度
imgLoaded(e) {
// console.log(e, wx.getSystemInfoSync())
this.setData({
imageHeight: e.detail.height *
(wx.getSystemInfoSync().windowHeight / e.detail.height)
})
}
//返回到首页
// _backhome() {
// wx.switchTab({
// url: '/pages/index/index'
// })
// }
}
})
/* componets/shoppingMall-components/navbar/navbar.wxss */
/* 顶部要固定定位 标题要居中 自定义按钮和标题要和右边微信原生的胶囊上下对齐 */
.nav-wrap {
/* display: none; */
position: fixed;
width: 100%;
top: 0;
background: #fff;
color: #000;
z-index: 9999999;
background: #000;
overflow: hidden;
}
/* 背景图 */
.backgroundimg {
position: absolute;
z-index: -1;
}
/* 标题要居中 */
.nav-title {
position: absolute;
text-align: center;
max-width: 400rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
font-size: 36rpx;
color: #2c2b2b;
font-weight: 450;
}
.nav-capsule {
display: flex;
align-items: center;
margin-left: 30rpx;
width: 140rpx;
justify-content: space-between;
height: 100%;
}
.back-pre {
width: 36rpx;
height: 40rpx;
margin-top: 4rpx;
padding: 10rpx;
}
.nav-capsule {
width: 36rpx;
height: 40rpx;
margin-top: 3rpx;
}
.iconxiangzuo{
font-size: 41rpx;
color: #fff;
}
.iconxiaochengxushouye {
font-size: 41rpx;
color: #fff;
}
在自定义导航栏中针对不同的进入情况对左上角的按钮做不同的处理,当是通过扫码或分享进入的时候左上角是返回首页的小房子,通过其他页面跳转过来时则是返回上一级的箭头。
<view wx:if="{
{visible}}" class="red-envelope-popup">
<view class="container flex-center">
<view bind:tap="handleClose" catch:touchmove="handleScrollTouch" class="close-bg"></view>
<block>
<block wx:if="{
{showStatus===2}}">
<view class="rain-wrapper flex-column">
<view class="canvas-wrapper">
<view class='score-change' animation="{
{scoreAni}}">
+{
{showChangeScore}}
</view>
<canvas disableScroll binderror="canvasIdErrorCallback" bindtouchstart="handleClickRain" canvasId="rain-canvas" style="width: 100vw; height: 100vh;z-index: 9999999;"></canvas>
</view>
</view>
</block>
<block wx:if="{
{showStatus===3}}">
<view class="result-wrapper flex-column-center">
<block>
<view class="group-content flex-column-center" bindtap="handleClose">
</view>
</block>
</view>
</block>
</block>
</view>
</view>
const innerAudioContext = wx.createInnerAudioContext()
const api = require('../../../server/api.js');
const APP = getApp()
let readyTimer = null
let rainTimer = null
let redEnvelopes = []
let animation = null
const minWidth = 30 // 红包图片最小宽度
const maxWidth = 40 // 红包图片最大宽度
Component({
properties: {
// 是否开始展示游戏
visible: {
type: Boolean,
value: false
},
// 游戏时间
time: {
type: Number,
value: 10
},
// 速度
createSpeed: {
type: Number,
value: 5
},
// 单个最小金额
min: {
type: Number,
value: 0
},
// 单个最大金额
max: {
type: Number,
value: 3
}
},
data: {
showRainTotalTime: 10, // 红包雨时间
showStatus: 1, // 红包雨状态:1:准备倒计时,2:正在红包雨,3:红包雨结束
windowWidth: 375,
windowHeight: 555,
rainResult: {
},
loading: false,
showScore: 0,
showChangeScore: 0,
scoreStyle: ''
},
ready: function() {
// 重置
redEnvelopes = []
// clearTimeout(readyTimer)
clearTimeout(rainTimer)
this.cancelCustomAnimationFrame(animation)
// 开始准备倒计时
this.showRain()
const {
windowWidth,
windowHeight
} = APP.globalData.systemInfo
this.data.windowWidth = windowWidth
this.data.windowWidth = windowHeight
},
detached: function() {
// readyTimer && clearInterval(readyTimer)
rainTimer && clearInterval(rainTimer)
animation && this.cancelCustomAnimationFrame(animation)
},
methods: {
// 展示红包雨界面
showRain: function() {
let _this = this
// 显示红包雨
this.setData({
showStatus: 2
})
// 初始化红包雨
this.initRain()
// 倒计时进度条
this.ininProgress()
// 红包雨倒计时
let showRainTotalTime = this.data.time
rainTimer = setInterval(function() {
if (--showRainTotalTime <= 0) {
clearInterval(rainTimer)
if (animation) {
// 结束
_this.showRainResult()
_this.cancelCustomAnimationFrame(animation)
}
}
_this.setData({
showRainTotalTime
});
}, 1000);
},
// 倒计时进度条
ininProgress() {
const {
time
} = this.data
const animation = wx.createAnimation({
duration: time * 1000
})
animation.translateX(-120).step()
this.setData({
progressAni: animation.export()
})
},
//分数动画
animationOfScore(x, y) {
const position = wx.createAnimation({
duration: 0
})
position.left(x).top(y).step()
this.setData({
scoreAni: position.export()
})
const animation = wx.createAnimation({
duration: 300,
timingFunction: 'ease'
})
animation.opacity(1).step()
setTimeout(function() {
animation.opacity(0).step()
this.setData({
scoreAni: animation.export()
})
}.bind(this), 10)
},
// 关闭
handleClose: function() {
this.triggerEvent("finish", this.data.showScore)
},
// 显示结果
showRainResult: function() {
// 结束动画
this.cancelCustomAnimationFrame(animation)
this.setData({
showStatus: 3,
rainResult: {
amount: 100
}
});
},
// 红包下落函数
customRequestAnimationFrame: function(e) {
let _this = this
let timer = setTimeout(function() {
e.call(_this), clearTimeout(timer);
}, 1000 / 60)
return timer
},
// 清除红包下落函数
cancelCustomAnimationFrame: function(e) {
e && (clearTimeout(e), animation = null)
},
// 开始下落
doDrawRain: function() {
const context = this.context
const {
windowWidth,
windowHeight
} = this.data
context.clearRect(0, 0, windowWidth, windowHeight)
for (let n = 0; n < redEnvelopes.length; n += 1) {
const i = redEnvelopes[n] // 红包
const {
x,
y,
vx,
vy,
width,
height,
open
} = i
const img = open ? this.openEnvelopeImg : this.redEnvelopeImg
const imgWidth = open ? width + 20 : width
const imgHeight = open ? height + 25 : height
if (x < 0) {
x += 50;
} else if (x > 750) {
x -= 50;
}
context.drawImage(img, x, y, imgWidth, imgHeight)
i.x += vx
i.y += vy
i.y >= windowHeight && (i.y = 0, i.open = false)
i.x + width <= 0 && (i.x = windowWidth - width, i.open = false)
}
context.draw()
// 下落函数
animation = this.customRequestAnimationFrame(this.doDrawRain);
},
// 随机数
randNum: function(min, max) {
return Math.floor(min + Math.random() * (max - min));
},
// 准备红包雨下落
initRainDrops: function() {
const {
windowWidth,
windowHeight,
createSpeed,
max,
min
} = this.data
for (let n = 0; n < 10; n += 1) {
const startX = Math.floor(Math.random() * windowWidth)
// 优化位置,防止红包越界现象,保证每个红包都在屏幕之内
if (startX < 0) {
startX += 50;
} else if (startX > windowWidth) {
startX -= 50;
}
const startY = Math.floor(Math.random() * windowHeight)
// 红包图片宽度大小30~40
const width = this.randNum(minWidth, maxWidth)
// 宽度为红包高度的百分之八十
const height = Math.floor(width / .8)
// 速度
const vy = 1 * Math.random() + createSpeed
// 红包金额
const score = this.randNum(min, max + 1)
redEnvelopes.push({
x: startX,
y: startY,
vx: -1, // x轴速度
vy: vy, // y轴速度
score: score,
width: width,
height: height,
open: false
});
}
this.doDrawRain();
},
// 点击红包事件
handleClickRain: function(e) {
let touch = e.touches[0]
let touchX = touch.x
let touchY = touch.y
let _this = this
for (let o = 0; o < redEnvelopes.length; o += 1) {
let i = redEnvelopes[o],
rainX = i.x,
rainY = i.y,
width = i.width,
height = i.height,
gapX = touchX - rainX,
gapY = touchY - rainY;
if (gapX >= -20 && gapX <= width + 20 && gapY >= -20 && gapY <= height + 20) {
_this.animationOfScore(touchX, touchY)
innerAudioContext.play()
i.open = true;
let score = _this.data.showScore + i.score
_this.setData({
showScore: score,
showChangeScore: i.score
})
break;
}
}
},
// 初始化 canvas
initRain: function() {
this.context = wx.createCanvasContext("rain-canvas", this)
this.redEnvelopeImg = "./images/red-packet-rain.png",
this.openEnvelopeImg = "./images/red-packet-rain-open.png"
// 初始化红包雨
this.initRainDrops()
// 音效
this.audioOfClick()
},
handleScrollTouch: function() {
},
audioOfClick() {
innerAudioContext.autoplay = false
innerAudioContext.src = 'https://imgs.solui.cn/weapp/dianji.mp3'
innerAudioContext.onPlay(() => {
})
innerAudioContext.onError(res => {
})
},
}
});
.flex {
display: -ms-flexbox;
display: flex
}
.flex-center {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: center;
justify-content: center;
width: 100%;
height: 100%
}
.flex-column {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column
}
.flex-column-center {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: center;
align-items: center
}
.flex-column-sb {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between
}
.flex-column-c {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: center;
justify-content: center
}
.flex-row {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center
}
.flex-row-center {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
width: 100%
}
.flex-content-center {
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
width: 100%
}
.flex-column-right {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: end;
align-items: flex-end
}
.flex-column-left {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: start;
align-items: flex-start
}
.flex-sb {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between
}
.flex-sa {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: distribute;
justify-content: space-around
}
.flex-c {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: center;
justify-content: center
}
.flex-sb-start {
display: -ms-flexbox;
display: flex;
-ms-flex-pack: justify;
justify-content: space-between
}
.flex-start {
display: -ms-flexbox;
display: flex;
-ms-flex-pack: start;
justify-content: flex-start
}
.flex-end {
display: -ms-flexbox;
display: flex;
-ms-flex-pack: end;
justify-content: flex-end
}
.flex-center-end {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: end;
justify-content: flex-end
}
.flex-end-center {
display: -ms-flexbox;
display: flex;
-ms-flex-align: end;
align-items: flex-end;
-ms-flex-pack: center;
justify-content: center
}
.flex-end-sb {
display: -ms-flexbox;
display: flex;
-ms-flex-align: end;
align-items: flex-end;
-ms-flex-pack: justify;
justify-content: space-between
}
.flex-end-start {
display: -ms-flexbox;
display: flex;
-ms-flex-align: end;
align-items: flex-end
}
.red-envelope-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
/* background: rgba(0,0,0,.8) */
}
.red-envelope-popup .close-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0
}
.red-envelope-popup .reminder-wrapper {
position: relative;
width: 750rpx;
height: 716rpx;
color: #fff;
box-sizing: border-box
}
.red-envelope-popup .reminder-wrapper .title {
font-size: 60rpx;
color: #fffdc5
}
.red-envelope-popup .reminder-wrapper .time {
margin-top: 100rpx;
font-size: 240rpx;
color: #fffdc5
}
.red-envelope-popup .rain-wrapper {
/* width: 100%; */
/* height: 100%; */
/* background-image: url(https://imgs.solui.cn/weapp/redBacketBG.jpg); */
/* background-size: 100% 100%; */
/* background-repeat: no-repeat */
}
.red-envelope-popup .rain-wrapper .time-info {
position: absolute;
top: 80rpx;
left: 45rpx;
font-size: 24rpx;
color: #fff
}
.red-envelope-popup .rain-wrapper .time-info .progress-wrapper {
position: relative;
height: 16rpx;
width: 240rpx;
margin-left: 20rpx;
margin-right: 20rpx;
border-radius: 16rpx;
background: #fff;
overflow: hidden
}
.red-envelope-popup .rain-wrapper .time-info .progress-wrapper .progress {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 240rpx;
border-radius: 16rpx;
background: #fe2e00;
z-index: 1
}
.red-envelope-popup .rain-wrapper .time-info .total-score {
font-size: 40rpx
}
.red-envelope-popup .rain-wrapper .canvas-wrapper {
position: relative
}
.red-envelope-popup .rain-wrapper .canvas-wrapper .score-change {
position: absolute;
width: 50rpx;
height: 50rpx;
font-size: 40rpx;
color: #fffdc5;
opacity: 0
}
.red-envelope-popup .result-wrapper {
width: 100%;
height: 100%;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center
}
.red-envelope-popup .result-wrapper .group-content {
position: relative;
width: 550rpx;
height: 700rpx;
/* background-image: url(https://imgs.solui.cn/weapp/[email protected]); */
background-image: url(http://hmspimg.afarsoft.com/static/crmmicroapp/res/shopImg/Demolition.png);
background-size: 100% 100%;
background-repeat: no-repeat
}
.red-envelope-popup .result-wrapper .group-content .result-title {
margin-top: 50rpx;
font-size: 40rpx;
color: #fff
}
.red-envelope-popup .result-wrapper .group-content .money-wrapper {
margin-top: 56rpx;
color: #fff
}
.red-envelope-popup .result-wrapper .group-content .money-wrapper .money {
font-size: 120rpx
}
.red-envelope-popup .result-wrapper .group-content .money-wrapper .unit {
position: relative;
top: 34rpx;
font-size: 32rpx
}
.red-envelope-popup .result-wrapper .group-content .result-btn {
margin-top: 158rpx;
width: 300rpx;
height: 70rpx;
background-color: #fff9e8;
text-align: center;
line-height: 70rpx;
border-radius: 40rpx;
font-size: 30rpx;
color: #b10000
}
在这个组件中,在attached
这个生命周期的时候进是红包下落的方法开始执行。在创建红包横向坐标的时候需要对坐标进行优化防止红包出现在屏幕外。
<!--RedEnvelopes/pages/RedEnvelopes/WinningRedEnvelope/WinningRedEnvelope.wxml-->
<view wx:if="{
{showMode}}">
<image src="{
{url}}" class="winning"></image>
<view class="congratulations" wx:if="{
{NotWinning}}">很遗憾!</view>
<view class="congratulations" wx:else>恭喜您!</view>
<view class="prize" wx:if="{
{NotWinning}}">您本次没有中奖。</view>
<view class="prize" wx:else>您一共获得<block wx:if="{
{RedEnvelope.AmountCount}}">{
{RedEnvelope.AmountCount}}个红包</block><block wx:if="{
{RedEnvelope.AmountCount && RedEnvelope.CouponCount}}">,</block><block wx:if="{
{RedEnvelope.CouponCount}}">{
{RedEnvelope.CouponCount}}张优惠券</block></view>
<block wx:for="{
{RedEnvelope.CouponWinInfoList}}" wx:key="index">
<view class="prize-details">
<view class="prize-quan">
<image src="{
{quan}}" class="quan"></image>
<text class="quan-txt" wx:if="{
{item.UseType == 0}}">{
{item.DiscountAmount}}元</text>
<text class="quan-txt" wx:else>{
{item.DiscountRate}}折</text>
<text class="quan-type coupon">优惠券</text>
</view>
<view class="prize-name">{
{item.CouponName}}</view>
<view class="prize-num">数量:{
{item.Num}}</view>
</view>
</block>
<block wx:for="{
{RedEnvelope.AmountWinInfoList}}" wx:key="index">
<view class="prize-details">
<view class="prize-quan">
<image src="{
{quan}}" class="quan"></image>
<text class="quan-txt">{
{item.RedEnvelopeAmount}}元</text>
<text class="quan-type cash">现金</text>
</view>
<view class="prize-name">{
{item.RedEnvelopeAmount}}元现金红包</view>
<view class="prize-num">数量:{
{item.Num}}</view>
</view>
</block>
<view class="share">
<view class="share-left">邀请好友参加活动</view>
<view class="share-left lower">每天可获得
<text class="share-num" wx:if="{
{NotWinning}}">{
{MaxSharingAwardTimes}}次</text>
<text class="share-num" wx:else>{
{RedEnvelope.MaxSharingAwardTimes}}次</text>分享奖励</view>
<button class="share-btn" open-type="share">
<text class="share-txt">去分享</text>
</button>
</view>
</view>
// RedEnvelopes/pages/RedEnvelopes/WinningRedEnvelope/WinningRedEnvelope.js
const api = require('../../../../server/api.js');
const http = require('../../../../server/request.js');
Page({
/**
* 页面的初始数据
*/
data: {
url: api.pictureServer + '/res/shopImg/winning.png',
quan: api.pictureServer + '/res/shopImg/redCoupon.png',
Id: '', //抢红包活动标识
PartId: '', //抢红包参与信息标识
RedEnvelope: {
},
ShareDescription: '', //分享描述
ShareIcon: '', //分享图标
NotWinning: true, //未中奖
showMode: false
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
console.log(options)
this.setData({
Id: options.Id,
PartId: options.PartId,
ShareDescription: options.ShareDescription,
ShareIcon: options.ShareIcon,
})
let parm = {
Id: options.Id,
PartId: options.PartId
}
this.getGrabRedEnvelopeWinInfo(parm);
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
let ShareMemberId = wx.getStorageSync('CustomerService').MemberId
return {
title: this.data.ShareDescription,
// desc: '快来抢红包雨',
imageUrl: this.data.ShareIcon,
path: '/RedEnvelopes/pages/RedEnvelopes/index/index?refer=' + this.data.Id + '&ShareMemberId=' + ShareMemberId,
success: function(res) {
// 转发成功
wx.showToast({
title: '分享成功',
icon: 'none'
})
},
fail: function(res) {
// 转发失败
}
}
},
// 拆开红包得到中奖信息
getGrabRedEnvelopeWinInfo: function(parm) {
let that = this;
http.requestLoading('/api/services/app/RedEnvelope/GetGrabRedEnvelopeWinInfo', parm, '', 'GET').then(res => {
if (res.data.Result.Code === 0) {
console.log(res)
that.setData({
NotWinning: false,
RedEnvelope: res.data.Result.Data,
showMode: true
})
} else if (res.data.Result.Code === 1) {
//未中奖
that.setData({
NotWinning: true,
MaxSharingAwardTimes: res.data.Result.Data.MaxSharingAwardTimes,
showMode: true
})
}
})
}
})
/* RedEnvelopes/pages/RedEnvelopes/WinningRedEnvelope/WinningRedEnvelope.wxss */
page {
width: 100%;
background: rgb(254, 177, 198, 0.2);
}
/* 礼品 */
.winning {
width: 286rpx;
height: 294rpx;
display: block;
margin: 20rpx auto;
}
.congratulations {
font-size: 46rpx;
font-family: Adobe Heiti Std R, Adobe Heiti Std R-R;
color: #ff3005;
text-align: center;
}
.prize {
font-size: 28rpx;
font-family: Adobe Heiti Std R, Adobe Heiti Std R-R;
color: #fc7297;
text-align: center;
margin: 38rpx auto;
}
/* 奖励内容 */
.prize-details {
width: 668rpx;
height: 150rpx;
background: #fff;
border-radius: 10rpx;
display: block;
margin: auto;
padding: 38rpx 0 38rpx 38rpx;
margin-bottom: 20rpx;
position: relative;
}
.prize-quan {
width: 177rpx;
position: relative;
}
.quan {
width: 177rpx;
height: 74rpx;
}
.quan-txt {
position: absolute;
top: 20rpx;
left: 30rpx;
font-size: 28rpx;
font-family: Source Han Sans CN Regular, Source Han Sans CN Regular-Regular;
font-weight: 400;
color: #fff;
}
.quan-type {
width: 19rpx;
font-size: 20rpx;
font-family: Source Han Sans CN Regular, Source Han Sans CN Regular-Regular;
font-weight: 400;
text-align: left;
color: #fff;
line-height: 22rpx;
}
.coupon {
position: absolute;
top: 7rpx;
right: 16rpx;
height: 63rpx;
}
.cash {
position: absolute;
top: 15rpx;
right: 16rpx;
height: 39rpx;
}
.prize-name {
font-size: 30rpx;
font-family: Source Han Sans CN Regular, Source Han Sans CN Regular-Regular;
font-weight: 400;
color: #333;
position: absolute;
top: 37rpx;
left: 244rpx;
}
.prize-num {
font-size: 24rpx;
font-family: Source Han Sans CN Regular, Source Han Sans CN Regular-Regular;
font-weight: 400;
color: #333;
position: absolute;
top: 86rpx;
left: 244rpx;
}
/* 分享 */
.share {
width: 668rpx;
height: 150rpx;
background: #f77b7b;
border-radius: 10rpx;
margin: 40rpx auto;
position: relative;
}
.share-left {
font-size: 30rpx;
font-family: Source Han Sans CN Normal, Source Han Sans CN Normal-Normal;
color: #fff;
position: absolute;
top: 32rpx;
left: 47rpx;
}
.lower {
position: absolute;
top: 73rpx;
left: 47rpx;
}
.share-num {
font-size: 38rpx;
}
.share-btn {
width: 170rpx;
height: 49rpx;
background: linear-gradient(0deg, #fa8d35 0%, #ffc341 100%);
border-radius: 25rpx;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 55rpx;
right: 35rpx;
}
.share-txt {
font-size: 30rpx;
font-family: Source Han Sans CN Normal, Source Han Sans CN Normal-Normal;
font-weight: 500;
color: #fff;
}
在查看红包界面中没有太多需要主要的东西,主要实现分享即可。
<!--RedEnvelopes/pages/RedEnvelopes/WinningRecord/WinningRecord.wxml-->
<wxs module="func">
module.exports = {
strSplit: function(str, con) {
return str.split(con);
}
}
</wxs>
<view>
<view class="calendar_container container">
<view class="select" bindtap="dateClick">
<view>{
{showStartDate||showEndDate?showStartDate+'-'+showEndDate:'全部'}}</view>
</view>
<i class="iconfont iconrili" bindtap="dateClick"></i>
</view>
<view class="container">
<block wx:for="{
{DrawRecordlist}}" wx:key="{
{index}}">
<view class="DrawRecordItem">
<view class="item0">
<view class="item_txt">{
{item.CreateTime}}</view>
</view>
<view class="item1">
<block wx:for="{
{item.HongBaoWinningRecordList}}" wx:key="{
{index}}">
<view class="item_txt dib mr-5">{
{item.RedEnvelopeAmount}}元红包
<text class="lookCode lookCodes">X{
{item.HongBaoNum}}</text>
</view>
</block>
<block wx:for="{
{item.CouponWinningRecordList}}" wx:key="{
{index}}">
<view class="item_txt dib mr-5">{
{item.CouponName}}
<text class="lookCode">X{
{item.CouponNum}}</text>
</view>
</block>
</view>
</view>
</block>
</view>
<CalendarComponent id="calendar-component" startDate="{
{startDate}}" endDate="{
{endDate}}" bind:myevent="getDate"></CalendarComponent>
</view>
// RedEnvelopes/pages/RedEnvelopes/WinningRecord/WinningRecord.js
// 引入URL
const http = require('../../../../server/request.js');
//调用公共js对象以便调用其方法
var app = require('../../../../utils/uselogn.js'); //获取应用实例
var util = require('../../../../utils/util.js') //公用方法
const api = require('../../../../server/api.js');
var pageNum = 1;
Page({
/**
* 页面的初始数据
*/
data: {
//组件数据
componentData: null,
// 仅支持 年-月-1
startDate: "1960/1/1",
endDate: new Date().getFullYear() + "/" + (new Date().getMonth() + 1) + "/" + new Date().getDate(),
pictureServer: api.pictureServer,
tabIndex: 0,
skinStyle: "",
userDaysAward: [], //用户签到奖励
signInReward: [], //连续签到奖励
showStartDate: "", //显示用的
showEndDate: "", //显示用的
time1: "",
time2: "",
DrawRecordlist: [],
ActivityId: ""
},
onLogin: app.userLogin,
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
pageNum = 1;
//创建自定义组件实例
this.setData({
componentData: this.selectComponent('#calendar-component')
})
this.setData({
skinStyle: wx.getStorageSync("skin")
})
if (options.ActivityId) {
this.setData({
ActivityId: options.ActivityId
})
}
this.onLogin(this.callback)
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
pageNum++;
this.getLotteryRecord();
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
},
callback() {
this.getLotteryRecord(this.data.time1,
this.data.time2)
},
/**
* tab切换
*/
//获取会员的中奖纪录信息
getLotteryRecord(s, e) {
http.requestLoading("/api/services/app/RedEnvelope/MemberWinningRecord", {
"StartDate": s ? s + " 00:00:00" : '',
"EndDate": e ? e + " 23:59:59" : '',
"page": pageNum,
"rows": 10
}, "", "POST").then(res => {
if (res.data.Result.Code === 0) {
if (!res.data.Result.Rows) {
wx.showToast({
title: '无更多数据了',
icon: "none"
})
return
}
this.setData({
DrawRecordlist: this.data.DrawRecordlist.concat(res.data.Result.Rows)
})
}
})
},
showClick(e) {
const {
index
} = e.currentTarget.dataset
const data = this.data.signInReward
data[index].show = !data[index].show
this.setData({
signInReward: data
})
},
//显示组件
dateClick() {
this.data.componentData.createAni()
},
//获取开始结束
getDate(e) {
const startDate = e.detail[0]
const endDate = e.detail[1]
this.setData({
showStartDate: startDate.replace(/-/g, '/'),
showEndDate: endDate ? endDate.replace(/-/g, '/') : "",
time1: startDate,
time2: endDate ? endDate : "",
DrawRecordlist: []
})
this.getLotteryRecord(startDate, endDate)
}
})
/* RedEnvelopes/pages/RedEnvelopes/WinningRecord/WinningRecord.wxss */
page {
background-color: #f5f6f7;
}
.container {
padding: 0 40rpx;
box-sizing: border-box;
}
/* 选择日历 */
.calendar_container {
width: 100%;
height: 86rpx;
display: flex;
align-items: center;
}
.select {
width: 320rpx;
height: 58rpx;
background: #fff;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: space-between;
color: #999;
font-size: 24rpx;
padding-left: 20rpx;
padding-right: 10rpx;
}
.iconrili {
margin-left: 22rpx;
font-size: 32rpx;
color: rgba(205, 205, 205, 0.97);
}
.DrawRecordItem {
margin-top: 16rpx;
background: #fff;
border-radius: 10rpx;
}
.item1 {
padding: 0 22rpx 22rpx 22rpx;
width: 100%;
background: #fff;
border-radius: 10rpx;
}
.lookCode {
font-size: 24rpx;
font-family: Source Han Sans CN Normal, Source Han Sans CN Normal-Normal;
font-weight: 400;
color: #4ace8c;
}
.lookCodes {
color: #fc7297;
}
.item0 {
padding: 0 16rpx 0 26rpx;
width: 100%;
height: 66rpx;
background: #fff;
border-radius: 10rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.item_txt {
font-size: 24rpx;
font-family: Source Han Sans CN Normal, Source Han Sans CN Normal-Normal;
font-weight: 400;
color: #666;
}
.mr-5 {
margin-right: 20rpx;
}
在中奖纪录查看的页面中使用的自己封装的日历组件。仅是为了复合项目的要求,在此就不做展示了。
红包雨整个流程到此就结束了。