// 泡泡随机漂浮
// 这里的封装逻辑是基于每个泡泡大小一样进行封装的
import React,{FC,useState,useEffect,useRef} from 'react'
import styles from './index.less'
// 泡泡的背景图
import bubble1 from '../../img/bubblefloat/bubble1.png'
import bubble2 from '../../img/bubblefloat/bubble2.png'
import bubble3 from '../../img/bubblefloat/bubble3.png'
import bubble4 from '../../img/bubblefloat/bubble4.png'
import bubble5 from '../../img/bubblefloat/bubble5.png'
import bubble6 from '../../img/bubblefloat/bubble6.png'
import bubble7 from '../../img/bubblefloat/bubble7.png'
const Bubble:FC<any> = (props)=>{
useEffect(()=>{
document.documentElement.style.fontSize=(document.documentElement.clientWidth/750)*100+'px';
},[])
const mainRef = useRef<any>()
const BubblesRef = useRef<any>([])
const getBubbles = (dom: any) => {
if(!dom) return;
BubblesRef.current.push(dom)
}
const bubblesArr = [
{key:1,bgImg:bubble1},
{key:2,bgImg:bubble2},
{key:3,bgImg:bubble3},
{key:4,bgImg:bubble4},
{key:5,bgImg:bubble5},
{key:6,bgImg:bubble6},
{key:7,bgImg:bubble7},
]
// 气泡运动的函数
const bubbleMovement = ()=>{
// 气泡运动区域
const main = mainRef.current
// 气泡数组
const circles = BubblesRef.current
//初始化运动的最大宽和高, 为了让气泡不卡在屏幕边缘,因此要减去自身的宽高
const maxW = main.clientWidth- circles[0].clientWidth;
const maxH = main.clientHeight - circles[0].clientHeight;
const container:any = []; //存放气泡的具体信息,包括坐标,速度等值
const cwidth = circles[0].clientWidth; //小球的直径
let arr;
//数组对象的初始化
for (let i = 0; i < circles.length; i++) {
arr = {};
arr.x = Math.floor(Math.random() * (maxW + 1)); //初始x坐标
arr.y = Math.floor(Math.random() * (maxH + 1)); //初始y坐标
arr.cx = arr.x + circles[0].clientWidth / 2; //圆心x坐标
arr.cy = arr.y + circles[0].clientHeight / 2; //圆心y坐标
arr.movex = Math.floor(Math.random() * 2); //x轴移动方向 1:右 0:左
arr.movey = Math.floor(Math.random() * 2); //y轴移动方向 1:下 0:上
arr.speed = 1 + Math.floor(Math.random() ); //随机移动距离
arr.timer = null; //计时器
arr.index = i; //索引值
container.push(arr); //存放全部的属性值
// 气泡位置初始化
circles[i].style.left = arr.x + 'px';
circles[i].style.top = arr.y + 'px';
}
// 碰撞检测函数
const crash = (a) => {
const bubble1x = container[a].cx; //在数组中任意球的圆心坐标
const bubble1y = container[a].cy;
for (let i = 0; i < container.length; i++) {
if (i !== a) { //判断取出来的气泡是不是本身,才能和其余球进行距离判断
const bubble2x = container[i].cx; //将其余气泡的圆心坐标赋值给气泡2
const bubble2y = container[i].cy;
//圆心距 求两个点之间的距离,开平方
const distence = Math.sqrt((bubble1x - bubble2x) * (bubble1x - bubble2x) + (bubble1y - bubble2y) * (bubble1y - bubble2y));
// //球心距离和求直径比较 碰撞
if (distence <= cwidth) {
//气泡1位于气泡2的右方
if (bubble1x > bubble2x) {
//气泡1位于气泡2的下方(右下)
if (bubble1y > bubble2y) {
container[a].movex = 1; //1表示为正值,对应的右和下
container[a].movey = 1; //0表示为负值,对应的左和上
}
//气泡1位于气泡2的上方(右上)
else if (bubble1y < bubble2y) {
container[a].movex = 1;
container[a].movey = 0;
}
//气泡1气泡2的垂直位置相同(水平)
else {
container[a].movex = 1;
}
}
//气泡1位于气泡2的左方
else if (bubble1x < bubble2x) {
//气泡1位于气泡2的下方(左下)
if (bubble1y > bubble2y) {
container[a].movex = 0;
container[a].movey = 0;
}
//气泡1位于气泡2的上方(左上)
else if (bubble1y < bubble2y) {
container[a].movex = 0;
container[a].movey = 1;
}
//气泡1气泡2的垂直位置相同(水平)
else {
container[a].movex = 0;
}
}
//气泡1和气泡2的水平方位位置相同(垂直)
else {
// 气泡1在气泡2正下方
if (bubble1y > bubble2y) {
container[a].movey = 1;
}
// 气泡1在气泡2正上方
else if (bubble1y < bubble2y) {
container[a].movey = 0;
}
}
}
}
}
}
//移动函数
const move = (bubble)=> { //每一个球单独有定时器
bubble.timer = setInterval(()=>{
//若是往右跑,则一直加速度,碰到边界,改成反方向运动
if (bubble.movex === 1) {
bubble.x += bubble.speed;
if (bubble.x + bubble.speed >= maxW) { //防止小球出界
bubble.x = maxW;
bubble.movex = 0; //小球运动方向发生改变
}
} else {
bubble.x -= bubble.speed;
if (bubble.x - bubble.speed <= 0) {
bubble.x = 0;
bubble.movex = 1;
}
}
//若是往下跑,则一直加速度,碰到边界,改成反方向运动
if (bubble.movey === 1) {
bubble.y += bubble.speed;
if (bubble.y + bubble.speed >= maxH) {
bubble.y = maxH;
bubble.movey = 0;
}
} else {
bubble.y -= bubble.speed;
if (bubble.y - bubble.speed <= 0) {
bubble.y = 0;
bubble.movey = 1;
}
}
// 计算坐标圆心是为了判断气泡和气泡之间是否碰撞
bubble.cx = bubble.x + circles[0].clientWidth / 2; //圆心等于:运动中x的值加上自身的半径
bubble.cy = bubble.y + circles[0].clientHeight / 2;
circles[bubble.index].style.left = bubble.x + 'px';
circles[bubble.index].style.top = bubble.y + 'px';
crash(bubble.index); //每一个小球进行碰撞检测
}, 30); //改变定时器的数值可以修改小球运动速度
}
// //对每个小球绑定计时器,让小球动起来
for (let i = 0; i < container.length; i++) {
move(container[i]);
}
}
useEffect(()=>{
bubbleMovement()
},[])
return(
<div className={styles.bubbles} ref={mainRef}>
{
bubblesArr.map((item)=>{
return <div key={item.key}
className={styles.bubble}
style={{ backgroundImage:`url(${item.bgImg})`}}
ref={getBubbles}/>
})
}
</div>
)
}
export default Bubble;
.bubbles{
width: 100vw;
height: 95vh;
position: relative;
.bubble{
position: absolute;
background-repeat: no-repeat;
background-size: contain;
width: 1rem;
height: 1rem;
}
}
1、实现方式:定位+随机改变x值和y值
2、需要注意的是:这里的实现逻辑是基于每个气泡的大小都一样的前提下实现的
3、具体的实现逻辑在bubbleMovement函数
(1)初始化,设置每个气泡的随机位置,移动距离、移动方向
(2)增加setInterval函数让每个气泡动起来 运动起来具体逻辑看move函数,大概的实现逻辑就是根据初始化的方向,往左或右或上或下以初始化时给定的速度走,走的时候需要监听是否到达边界,如果到达,继续往反方向走,走的时候并且需要监听是否碰撞
(3)监听碰撞:具体看这个函数 crash(bubble.index);这个函数大概的逻辑就是判断一下有没有碰撞到别的气泡,如果碰撞了,就判断一下当前气泡处于这个气泡的哪个方位,如果在左上方,那就继续往这个左上方走(既然碰撞了 并且气泡1位于气泡2的左上方,就说明之前气泡1运动的方向是右下才会导致碰撞)