文章目录

  • 红包雨实现(优化版)
    • 实现效果![在这里插入图片描述](https://img-blog.csdnimg.cn/976d39a293234b5780a3d74b0a3360a6.gif)
    • 优化内容
    • 使用框架
    • 实现原理
    • 不足之处
    • 使用示例
    • 代码实现

红包雨实现(优化版)

实现效果

优化内容

1、优化了点击灵敏度问题,将click改成touchstart(为啥要修改下面有说)
2、优化了重叠严重的问题
3、使用spritejs框架生成红包,不再操作原生dom
4、增加了暂停游戏、继续游戏的功能
5、支持配置多种样式大小的红包
6、增加了一下其他配置功能去控制界面元素的相关显示

使用框架

spritejs
http://spritejs.com/#/

实现原理

第一步:根据传入的红包尺寸数组计算出平均宽度和平均长度(init方法)
第二步:调用createRedPacketRain方法创建场景
第三步:通过create给定红包的位置(重点)
该方法的实现实际上就是根据平均宽度和平均长度得出屏幕上一行和一列分别能放置多少元素
然后随机取行列位置(取出来后将该位置删除 则下一个元素无法再次放置在这个位置),生成红包放置在屏幕上
第三步:得到位置,调用createRedPacket在屏幕上生成红包并设置触碰事件与相关动画
第四步:当单个红包被点击或者下落到底部,从屏幕上移除这个元素,重新调用create()方法在屏幕上随机生成一个元素

主要就是弄清楚spritejs的使用已经生成位置的方法就可以很快的理解这个组件的实现了

不足之处

1、为了避免红包的左右位置每次都固定间隔无法错落,设置了一个左右的随机偏移,这就可能导致红包的部分重叠
2、因为每次点击和红包下落到底部都要从新在屏幕取一个位置生成,但这个时候没法判断取的这个点是不是有红包,所以有可能导致红包重叠
3、之前因为是用的click时间(移动端为了监控双击事件,所以click时间会有300ms的延时,但是我看了一些支付之类的抢能量的案例 也是不给过快点击的 可是产品他们觉得这样用户体验不好,快速点击的时候有写红包抢不到,于是改成了touchStart事件 但我觉得这种过于灵敏,后续若真需要控制红包点击的灵敏度,可修改事件触发,用touch的事件模拟一个点击事件出来)

使用示例

import React,{FC, useEffect, useRef, useState} from "react";
import styles from './index.less'
import RedpacketRain,{IHandle as RedpacketRainHandle} from '@/components/redpacketRain'const Test:FC<any> = (props)=>{const shareConfigRef = useRef<RedpacketRainHandle>(null);const gameOver = (score)=>{console.log("游戏结束",score)} // 重启游戏const again = ()=>{shareConfigRef.current?.restart()}// 关闭弹窗const handleClose =()=>{}return(<div className={styles.test} ><RedpacketRain redpacketArr={[redpacket1,redpacket2,redpacket3,redpacket4,redpacket5]}redpacketSize={new Array(5).fill({redpacketWidth:138/2.3,redpacketHeight:213/2.3})}ref={ConfigRef}time={30} gameOver={gameOver}basenum={10}visible={gameVisible}isincline={false}speed={3000}needCloseBtn={false}addOne={addOne}needReadygo={false}/></div>)
}export default Test

代码实现

import React, {FC,useEffect,useRef,useState,forwardRef,useImperativeHandle,
} from "react";
import { Gradient, Group, Label, Scene, Sprite } from "spritejs";
import CommonPopUp from "../commonPopUp"; //公司封装的弹窗组件
import styles from "./index.less";
import addOnepng from "./img/addOne.png";
import ready from "./img/ready.png";
import go from "./img/go.png";
import { Toast } from "antd-mobile";interface Props {/*** 是否需要Ready go提示*/needReadygo?: boolean;/*** 红包素材数组*/redpacketArr: Array<any>;/*** +1素材*/addOne?: any;/*** 红包素材大小*/redpacketSize: Array<{ redpacketWidth: number; redpacketHeight: number }>;/*** 红包下落速率种子配置 数值越大 下落速度越慢*/speed: number;/*** //红包基数* 可通过这个参数控制屏幕上显示的红包数量*/basenum: number;/*** 游戏时间*/time: number;/*** 总参与机会*/joinTime?: number;/*** 剩余参与机会*/remainJoinTime?: number;/*** 该红包雨弹窗是否可见*/visible: boolean;/*** 红包是否倾斜 默认值false*/isincline?: boolean;/*** 游戏结束后的回调* 传入游戏得分*/gameOver: (score) => void;/*** 是否需要关闭按钮*/needCloseBtn: boolean;/*** 关闭游戏的函数* 传入游戏得分*/handleClose?: (score?: number) => void;
}export interface IHandle {/*** 重启游戏*/restart: () => void;/*** 暂停游戏*/paused: () => void;/*** 继续游戏*/continute: () => void;
}const RedpacketRain = forwardRef<IHandle, Props>((props, ref) => {const {handleClose,gameOver,basenum,time,joinTime,remainJoinTime,visible,speed,isincline,redpacketArr,redpacketSize,needCloseBtn,addOne,needReadygo,} = props;const [myScore, setMyScore] = useState(0);const scoreRef = useRef<any>(0);scoreRef.current = myScore;const [countDown, setCountDown] = useState(0);const countDownRef = useRef<any>(0);countDownRef.current = countDown;const timerRef = useRef<any>();const containerRef = useRef<any>();const spriteRef = useRef<any>();const [readygoStep, setreadygoStep] = useState(0);const [H, setH] = useState<any>(0);const [W, setW] = useState<any>(0);useEffect(() => {if (visible) {// 先ready go 在执行begin函数if (needReadygo) {setreadygoStep(1);setTimeout(() => {setreadygoStep(2);setTimeout(() => {setreadygoStep(0);begin();}, 700);}, 1000);} else {begin();}} else {if (timerRef.current) {clearInterval(timerRef.current);}setMyScore(0);setCountDown(0);init();}}, [visible]);useEffect(() => {init();}, []);const begin = () => {if (timerRef.current) {clearInterval(timerRef.current);}setCountDown(time);setMyScore(0);// 设置倒计时timerRef.current = setInterval(() => {if (countDownRef.current <= 0) {clearInterval(timerRef.current);setTimeout(() => {gameOver(scoreRef.current);}, 500);return;}setCountDown(countDownRef.current - 1);}, 1000);createRedPacketRain(basenum);};// 初始化一张图(二维数组)格子法const init = () => {let sumH = 0;let sumW = 0;redpacketSize.map(item => {sumW += item.redpacketWidth;sumH += item.redpacketHeight;});// 素材宽高(取平均值)const H = Math.ceil(sumH / redpacketSize.length) + 10;const W = Math.ceil(sumW / redpacketSize.length);setH(H);setW(W);};const createRedPacketRain = basenum => {if (!basenum || basenum <= 0) {Toast.info("请设置有效的红包基数");return;}const container = containerRef.current;if (!container) return;const width = container.clientWidth;const height = container.clientHeight;// 创建场景const scene = new Scene({container,width: width,height: height,mode: "stickyTop",});// 层级const layer = scene.layer();spriteRef.current = layer;// 创建红包const createRedPacket = (src,redpacketWidth,redpacketHeight,xStart,yStart) => {//  创建包裹层const wrapper = new Group({pos: [xStart, yStart], //初始化位置});// 红包图片const rp = new Sprite(src);// +1提示const addone = new Sprite(addOne || addOnepng);// 光晕const haolo = new Sprite();// 红包属性设置rp.attr({pos: [0, 0], //图片位置size: [redpacketWidth, redpacketHeight], //图片大小transform: isincline ? `rotate(20deg)` : `rotate(0deg)`, // 控制倾斜});// +1属性设置addone.attr({pos: [126 / 1.5, 0],size: [25, 25],opacity: 0,zIndex: -1,});// 光晕属性设置//@ts-ignorehaolo.attr({size: [80, 80],borderRadius: 40,pos: [0, 0],opacity: 0,zIndex: -1,bgcolor: new Gradient({vector: [40, 40, 10, 40, 40, 80],/*vector: [x0,y0,r0,x1,y1,r1],● x0:定义渐变的开始圆的 x 坐标● y0:定义渐变的开始圆的 y 坐标● r0 :定义开始圆的半径● x1:定义渐变的结束圆的 x 坐标● y1:定义渐变的结束圆的 y 坐标● r1:定义结束圆的半径*/colors: [{ offset: -0.2, color: "#00000000" },{ offset: 0.3, color: "#b14a1ecf" },{ offset: 0.8, color: "#f4e259c2" },],}),});// 注册事件监听(touch事件)rp.addEventListener("touchstart", () => {if (countDownRef.current <= 0) {return;}setMyScore(scoreRef.current + 1);rp.setAttribute("width", 0);wrapper.setAttribute("zIndex", -99); //设置为 -99 不挡住别的元素addone.animate([{ pos: [126 / 1.5, 0], opacity: 1, scale: 1 },{ pos: [126 / 1.5, -300], opacity: 0, scale: 1.3 },],{duration: 1000,fill: `forwards`, // 动画定格在最后一帧}).finished.then(() => {addone.setAttribute("width", 0);wrapper.setAttribute("width", 0);});haolo.animate([{ opacity: 1, scale: 1 },{ opacity: 0, scale: 1.5 },],{duration: 500,fill: `forwards`, // 动画定格在最后一帧}).finished.then(() => {haolo.setAttribute("width", 0);wrapper.remove();create(1);});});// 外层属性设置// 红包下落动画const animate = wrapper.animate([{ pos: [xStart, yStart] }, { pos: [xStart, height] }],{duration: speed + (-yStart / height) * speed,fill: `forwards`, // 动画定格在最后一帧easing: "linear",});animate.finished.then(() => {if (countDownRef.current <= 0) {return;}wrapper.remove();create(1);});wrapper.appendChild(addone);wrapper.appendChild(haolo);wrapper.appendChild(rp);layer.append(wrapper);};// 生成位置代码const create = basenum => {// 随机取一个红包const key = Math.floor(Math.random() * redpacketArr.length);const src = redpacketArr[key];const size = redpacketSize[key];const { redpacketWidth, redpacketHeight } = size;//当放置的元素的宽高大于浏览器窗口的宽高时,直接返回if (redpacketWidth > width || redpacketHeight > height) {return false;}// 一些计算// 横向可以分成多少等分const xNum = Math.floor(width / W);// 纵向可以分成多少等分const yNum = Math.floor(height / H);const allNum = xNum * yNum; //浏览器窗口内总共可以放置元素的个数//当需要放置的元素的个数超过浏览器窗口内总共可以放置的元素的个数时,则返回let num = basenum;if (basenum >= allNum) {console.log("显示基数已经超出屏幕所能放置的元素个数了,设置新的基数值",allNum - 1);num = allNum - 1;}let _tmpArray: Number[] = [];for (let i = 0; i < allNum; i++) {_tmpArray.push(i);}let xStart = 0,yStart = 0;while (num) {const pointer = Math.floor(Math.random() * allNum); //向下取整取出0到allnum之间的任意一个整数//如果数组_tmpArray中不存在第pointer值,则继续if (!_tmpArray[pointer]) {continue;}delete _tmpArray[pointer]; //删除数组_tmpArray中第pointer个值yStart = parseInt(pointer / xNum + "", 10) * H + H;const xramdom = Math.round(Math.random() * (W * 0.4)) - W * 0.2;xStart = (pointer % xNum) * W + xramdom;// 在屏幕上创建这个元素createRedPacket(src, redpacketWidth, redpacketHeight, xStart, -yStart);num--;}};create(basenum);};// 暂停const paused = () => {clearInterval(timerRef.current);spriteRef.current.timeline.playbackRate = 0;};// 继续const continute = () => {if (timerRef.current) {clearInterval(timerRef.current);}// 重新开始倒计时spriteRef.current.timeline.playbackRate = 1;// 设置倒计时timerRef.current = setInterval(() => {if (countDownRef.current <= 0) {clearInterval(timerRef.current);setTimeout(() => {gameOver(scoreRef.current);}, 500);return;}setCountDown(countDownRef.current - 1);}, 1000);};useImperativeHandle(ref, () => ({restart: begin,continute,paused,}));return (<CommonPopUp visible={visible}><div className={styles.main}>{readygoStep === 1 && <img className={styles.ready} src={ready} />}{readygoStep === 2 && <img className={styles.go} src={go} />}{!readygoStep && (<div className={styles.topWrapper}>{needCloseBtn ? (<divclassName={styles.back}onClick={() => handleClose && handleClose(myScore)}/>) : null}<divclassName={styles.tip}style={{ marginTop: needCloseBtn ? "-0.5rem" : "0.5rem" }}><div className={styles.jointime}>{joinTime && remainJoinTime ? (<span>参与机会:{remainJoinTime}/{joinTime}</span>) : null}</div><div className={styles.time}>{countDown}S</div><div className={styles.num}>分数:{myScore}</div></div></div>)}<div className={styles.redpacket} ref={containerRef} /></div></CommonPopUp>);
});export default RedpacketRain;
.main {position: relative;z-index: 99;width: 100vw;height: 100vh;background-color: rgba(0, 0, 0, 0.4);.redpacket {width: 100vw;height: 100vh;position: absolute;top: 0;left: 0;}.ready {width: 4.06rem;position: absolute;top: 50%;left: 50%;transform: translateX(-50%);}.go {width: 2.23rem;position: absolute;top: 50%;left: 50%;transform: translateX(-50%);}.topWrapper {position: absolute;top: 0;left: 0;width: 100%;.back {width: 0.26rem;height: 0.38rem;background-repeat: no-repeat;background-size: contain;background-image: url(./img/back.png);margin: 0.4rem 0.3rem;}.tip {display: flex;justify-content: space-between;align-items: center;color: #ffedd3;font-size: 0.3rem;padding: 0 0.3rem;margin-top: 0.5rem;div {width: 30%;}.time {width: 1.7rem;height: 1.7rem;border-radius: 50%;border: 0.03rem #ffedd3 solid;text-align: center;line-height: 1.7rem;font-size: 0.6rem;}}}
}

红包雨实现(优化版)相关推荐

  1. 小程序中关于红包雨的实现

    一.原型依据 在我这个项目中小程序端所需要实现的只有红包雨的下落动画和通屏背景图的兼容,关于红包点击金额的计算是由后端实现的.首先来看下需要实现的效果图. 二.实现代码 首先是第一次进入的页面,在这个 ...

  2. QQ虎年春节活动ADB自动助手(自动开星星,自动红包雨下拉,自动团圆饭,自动一笔连)

    QQ虎年春节活动ADB自动助手(自动开星星,自动红包雨下拉,自动团圆饭,自动一笔连) 项目地址:GITHUB QQ虎年春节活动ADB助手 可以进去瞧瞧,顺便别忘了:即便是登录也要给我的博客点个赞啊喂! ...

  3. android红包雨动画,过年了,下场红包雨吧

    过年了,下场红包雨吧 过年了,使用属性动画和自定义view做了个下红包雨的动画,单机版. 效果图: 模拟器鼠标点击效果不是很好,真机上会好很多. 代码,先自定义一个红包View,画出需要的红包图片,当 ...

  4. 前端实现红包雨功能_最全解密微信红包随机算法(含代码实现)

    code小生 一个专注大前端领域的技术平台公众号回复 Android加入安卓技术群 "  1.引言 这个系列文章已经整理了10篇,但都没有涉及到具体的红包算法实现,主要有以下两方面原因.一方 ...

  5. 红包雨高并发问题解决方案

    问题 很多APP为了引流营销创造除了各种玩法,其中红包雨就是常见的一种方式,主要效果就是预告在某个时间点,会发布多少金额的券等等,到点就能够聚集上百万用户来抢,大概效果就是疯狂的戳屏幕,然后偶现几个金 ...

  6. 百度9亿拿下春晚,今日头条掷10亿红包雨...狂欢背后,互联网圈的品牌“混战”...

    又到了一年一度参与数十亿元大项目--开启春节红包大战的时候了! 春节一直被认为是一个观察互联网产品的特殊时期,阶层流动.人群汇聚,尤其对社交产品来说,是一个爆发的好时机,微信红包就是在春节期间崛起的. ...

  7. 微信小程序实现“红包雨”

    今天有个小师妹来问我怎样用微信小程序实现红包雨效果,如果用web很好实现,但是小程序不是那么容易,整合自己也有一年没摸过小程序了,决定试一试. 首先明确"红包雨"的需求: 随机位置 ...

  8. YOLO3升级优化版!Poly-YOLO:支持实例分割!

    YOLO3升级优化版!Poly-YOLO:支持实例分割! POLY-YOLO: HIGHER SPEED, MORE PRECISE DETECTION AND INSTANCE SEGMENTATI ...

  9. 小程序大转盘红包雨营销组件

    前言 商城没几个营销活动能叫商城吗? 所以就来几个组件吧,写的不好轻踩,对你有帮助记得给个小星星哦 直接上链接github链接 运行例子 git clone https://github.com/su ...

最新文章

  1. 计算机考试题 实操,计算机考试实操题-20210604194811.docx-原创力文档
  2. linux lvm 调整分区大小,linux调整lvm分区大小(/home分区过大,/root分区过小)
  3. C#/Net代码精简优化技巧(1)
  4. 【BZOJ1070】【codevs2436】修车,费用流
  5. 一定质量的封闭气体被压缩后_单螺杆压缩机
  6. 计算机跨考英语口译,英语口译学习网_考研复试经验:跨考过来人谈复试感受_沪江英语...
  7. 大容量NoSql解决方案:Aerospike实战
  8. 0084-CYX的异己
  9. 欧姆龙plc OMRON SYSMAX CP1H-E 使用 CXONE_V4.60 连接和编程
  10. 代码检查、评审、单元测试工具 大搜集
  11. Java日志系列——概述,JUL
  12. Unsupported Modules Detected: Compilation is not supported for following modules: app, library. Unfo
  13. Ubuntu18.04 运行velodyne
  14. 巴塞罗那,高迪的城市
  15. Git - 时光机穿梭
  16. 怎样制作BT种子文件,即torrent后缀的文件?
  17. Facebook关闭面部识别系统
  18. linux中的几种文件类型
  19. java基础案例3-2银行存取款
  20. 笔记--javascript对象及简单,复杂数据类型

热门文章

  1. springboot关闭web端口
  2. C++不同的数据成员类型
  3. firefox的html校验与dreamweave
  4. “第一性原理”学习摘要
  5. ArcBlock 启动去中心身份推广计划
  6. ajax的使用完整步骤,Ajax的使用四大步骤
  7. Python reversed函数及用法
  8. java计算机毕业设计的工资管理系统源码+mysql数据库+系统+lw文档+部署
  9. 511遇见易语言读入文件和写到文件
  10. 美元贬值对我国经济的影响(ZT)