提示:该实现方法灵敏度似乎不高 并且如果一直不点击红包,或者触发点击过慢,会出现屏幕上红包过多的问题(就是说不能让屏幕上的红包一直保持在一个均匀的量),另外一个缺点就是红包会重叠
效果
React代码
// 红包雨游戏
import React, { useEffect, useRef, useState } from "react"
import styles from './index.less'
const Game = ()=>{
useEffect(()=>{
document.documentElement.style.fontSize=(document.documentElement.clientWidth/750)*100+'px';
// 执行红包雨创建函数
createRedPacket()
setTimeout(()=>{
countDown()
},500)
},[])
const [nums,setNums] = useState(0)
const [countdown,setTime] = useState(15)
const ref = useRef<any>()
const timeRef = useRef<any>()
const timerRef = useRef<any>()
timeRef.current = countdown
const numsref = useRef<any>()
numsref.current = nums
// 红包雨元素创建
const createRedPacket = ()=>{
// 红包个数
const rainCount = 38
const rainFragment = document.createDocumentFragment()
let delay = 0;
for (let i = 0; i < rainCount; i++) {
let container = document.createElement('div');
// 光晕
let halo = document.createElement('div');
let rainEle = document.createElement('div');
let plusOne = document.createElement('div');
// 设置一下点击区域 红包图片背景点击区域太大了
let click = document.createElement('div');
container.className = `${styles.container}`;
click.className = `${styles.rainclick}`;
rainEle.className = `${styles.rain}`;
plusOne.className = `${styles.plusOne}`;
halo.className = `${styles.halo}`;
const offset = 10;
click.onclick = ()=>{
if( timeRef.current<=0) return; //倒计时结束 点击失效
plusOne.style.animation=`${styles.plusOne} 1s ease-in-out`
setNums(numsref.current+1)
rainEle.style.animation=`${styles.rainClick} 0.1s ease-in-out forwards`
halo.style.animation=`${styles.halo} 1s ease-in-out forwards`
container.style.animationPlayState="paused"
}
container.appendChild(plusOne)
container.appendChild(rainEle)
container.appendChild(halo)
rainEle.appendChild(click)
// 随机生成5-75的整数
const leftPosition = Math.round(Math.random() * (75-5))+5;
container.style.left = `${leftPosition}%`
container.style.animation = `${styles.raindrop} 8s infinite ease-in-out` // infinite 无效循环 所以红包雨的动画是循环播放的
// container.style.animationDelay = (Math.random()*14).toFixed(2) + 's';
container.style.animationDelay =(delay)+'s';
delay+=0.4;
plusOne.addEventListener("animationend",()=>{
// console.log("+1动画结束 移除动画 下一次点击时重新设置动画")
plusOne.style.animation=""
})
halo.addEventListener("animationend",()=>{
// console.log("光晕动画结束 移除动画 下一次点击时重新设置动画")
halo.style.animation=""
// 这样处理看看红包点击不了的问题能否解决 之前可能是因为层级
// container.style.display="none" //这样ios可能会有兼容性问题出现?
// 所以直接更改宽度让这个div消失
container.style.width="0"
})
// 监听动画一次循环结束 一次循环结束之后需要重新设置位置
container.addEventListener("animationiteration",()=>{
rainEle.style.animation=""
// 倒计时结束 不在播放红包雨动画
if( timeRef.current<=0){
// 移除动画
container.style.animation=""
// setBegin(false)
return;
}
// 重新设置位置
let leftPosition
if(i%2==0){
leftPosition = offset+Math.round(Math.random() * (75-5))+5;
}else{
leftPosition = -offset+Math.round(Math.random() * (75-5))+5;
}
container.style.left = `${leftPosition}%`
})
rainFragment.appendChild(container)
}
ref.current.appendChild(rainFragment)
}
// 倒计时
const countDown = ()=>{
timerRef.current = setInterval(()=>{
const countdown = timeRef.current
setTime(countdown-1)
if(countdown===1) {
clearInterval(timerRef.current )
}
},1000)
}
return(
<div className={styles.Game}>
<div className={styles.top}>
<div className={styles.countdown}>{countdown}S</div>
<div className={styles.time}>点击的红包数:{nums}</div>
</div>
<div className={styles.main} ref={ref}>
</div>
</div>
)
}
export default Game
css代码
.Game{
height: 92vh;
width: 100vw;
background: #808080c2;
overflow-y: hidden;
.top{
display: flex;
justify-content: center;
align-items: center;
color: #FFEDD3;
.time{
font-size: 0.3rem;
font-weight: 500;
margin: 0 0.5rem;
}
.countdown{
width: 1.7rem;
height: 1.7rem;
border: 0.01rem white solid;
border-radius: 50%;
font-size: 0.6rem;
text-align: center;
font-weight: bold;
line-height: 1.7rem;
}
position: relative;
}
// 红包雨
.main{
position: absolute;
top: -2.5rem;
width: 100%;
height: 100%;
// 被点击的光晕
.halo{
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
background-image: radial-gradient(circle at 50% 50%,rgba(0, 0, 0, 0),rgba(177, 74, 30, 0.81),rgba(244, 226, 89, 0.76) );
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
.container{
position: absolute;
background-clip: content-box;
box-sizing: border-box;
width: 1.88rem;
height: 2.01rem;
transform: scale(0.8);
}
.rain{
position: absolute;
top: 0;
left: 0;
width: 1.88rem;
height: 2.01rem;
background-image: url(../../img/rain/rain.png);
background-size: cover;
background-repeat: no-repeat;
}
.rainclick{
position: absolute;
width: 1.1rem;
height: 1.45rem;
left: 0.4rem;
top: 0.32rem;
transform: skew(-33deg,32deg);
z-index: 9;
}
.plusOne{
width: 1rem;
height: 1rem;
transform: scale(0.8);
background-image: url(../../img/rain/plus1.png);
background-size: cover;
background-repeat: no-repeat;
position: absolute;
top: -0.3rem;
left: 0;
opacity: 0;
z-index: 3;
}
}
}
// 红包雨的动画
@keyframes raindrop {
0% {
opacity: 1;
}
100% {
/* 1vh表示窗口高度的1% */
transform: scale(0.8) translate(0, calc(150vh)) ;
opacity: 1;
}
}
// 红包被点击的动画
@keyframes rainClick {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
// +1的动画
@keyframes plusOne{
20%{
opacity: 1;
}
100%{
transform: translateY(-1.5rem);
opacity: 0;
}
}
// 被点击的光晕的动画
@keyframes halo{
from{
opacity: 0.3;
}
to{
transform: translate(-50%,-50%) scale(1.5);
opacity: 0;
}
}
总结
1、实现方法就是css的循环动画,然后监听每次动画循环的结束,重新设置红包的横向位置
2、怎样控制红包不会一起下落呢?使用动画延时,让后面的红包下落动画延迟执行:
container.style.animationDelay =(delay)+‘s’;
delay+=0.4;
3、倒计时结束的时候,清除css动画,并且让点击事件失效
4、代码中用了很多ref是为了读取到state里面更新的最新的值,因为闭包的问题,如果不使用ref会一直读取到初始值的