悬浮球 可拖拽-支持鼠标和触控–react写法
直接上代码,js代码片段如下
import React, { useRef, useState, useEffect } from 'react';
import { useHistory, withRouter } from 'react-router-dom';
import './index.css';
let firstTime = 0, lastTime = 0;
const FloatingBall = (props) => {
const history = useHistory();
const [position, setPosition] = useState({ x: 1362, y: 782 });
const [positionChildern, setPositionChildern] = useState({ x: 60, y: 60 });
const [isShow, setIsShow] = useState(false);
const ballSize = 64;
//悬浮球点击
const onClick = (event) => {
if ((lastTime - firstTime) > 200) return
setIsShow(!isShow)
event.preventDefault();
}
//菜单点击
const menuClick = (event, url) => {
if (url.indexOf(getUrl()) > -1) {
history.push(url.replace(getUrl(), ''))
} else {
window.location.href = url
}
event.preventDefault();
}
const getUrl = () => {
let baseUrl = ''
switch (process.env.REACT_APP_ENV) {
case 'test':
baseUrl = 'http://...'
break
case 'prod':
baseUrl = 'https://...'
break
default:
baseUrl = 'https://...'
}
return baseUrl
}
useEffect(() => {
if (isShow) {
const screenWidth = document.documentElement.clientWidth;
const screenHeight = document.documentElement.clientHeight;
const baseWidth = 1920 / screenWidth;
const baseHeight = 1080 / screenHeight;
var height = document.getElementsByClassName("custom-menu-box")[0].offsetHeight;
let childernX = position.x > (screenWidth / 2) * baseWidth ? -250 : 30
let childernY = position.y > (screenHeight / 2) * baseHeight ? -(height - 30) : 30
setPositionChildern({ x: childernX, y: childernY })
}
}, [isShow]);
useEffect(() => {
//为true时会影响拖拽后的逻辑,只能为false进行设置
if (!props.isBallshow) {
setIsShow(props.isBallshow)
}
}, [props.isBallshow]);
const ballRef = useRef(null);
const handleMouseDown = (e) => {
firstTime = new Date().getTime();
const ball = ballRef.current;
let newX, newY;
const handleMouseMove = (e) => {
lastTime = new Date().getTime();
if (isShow) {
setIsShow(false)
}
//支持触控
const newLeft = e.touches ? e.touches[0].clientX : e.clientX;
const newTop = e.touches ? e.touches[0].clientY : e.clientY;
// 使用 document.documentElement.clientHeight 获取视口高度
const screenWidth = document.documentElement.clientWidth;
const screenHeight = document.documentElement.clientHeight;
const baseWidth = 1920 / screenWidth;
const baseHeight = 1080 / screenHeight;
// 边界检测,确保悬浮球不会超出视口---(ballSize/2)是球球一半值,处理鼠标在球球中心
if (newLeft >= (ballSize / 2) && newLeft - (ballSize / 2) <= screenWidth - ballSize) {
newX = `${baseWidth * newLeft}`;
}
if (newTop >= (ballSize / 2) && newTop - (ballSize / 2) <= screenHeight - ballSize) {
newY = `${baseHeight * newTop}`;
}
if ((lastTime - firstTime) < 200) return
setPosition({ x: newX - (ballSize / 2), y: newY - (ballSize / 2) });
};
const handleMouseUp = (e) => {
lastTime = new Date().getTime();
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('touchmove', handleMouseMove);
document.removeEventListener('touchend', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('touchmove', handleMouseMove);
document.addEventListener('touchend', handleMouseUp);
};
return (
<div
ref={ballRef}
className="floating-button"
style={{ left: position.x + 'px', top: position.y + 'px' }}
onMouseDown={handleMouseDown}
onTouchStart={handleMouseDown}
onClick={onClick}
>
<span></span>
{(isShow) && <div className='custom-menu-box' style={{ left: positionChildern.x, top: positionChildern.y }}>
{props.menuList && props.menuList.map((item, index) => (
<div onClick={e => { menuClick(e, item.url) }} className={index == props.menuList.length - 1 ? 'custom-last-div' : ''}>{item.resourceName}<i className='operate-L1Arr-open'></i></div>
))}
</div>}
</div>
);
};
export default FloatingBall;
在公共页面引入此组件,需要传入两个参数(menuList,isBallshow),我这个写的支持悬浮球点击 打开菜单,菜单的列表可跳转,鼠标拖拽和移动端触控都可以使用
不论哪个框架,拖拽的事件都差不多,自行修改就可以
css代码
* {
box-sizing: border-box;
}
.floating-button {
position: fixed;
width: 64px;
height: 64px;
cursor: pointer;
z-index: 1000;
top: 50%;
left: 50%;
/* background-color: red; */
}
.floating-button span {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-image: url('../../assets/images/ball/button.gif');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
z-index: 222;
}
.floating-button .custom-menu-box {
width: 280px;
/* height: 300px; */
background-color: #0C2243;
position: absolute;
top: 60px;
left: 60px;
border-radius: 8px;
border: 2px solid #379BE8;
padding: 8px 24px 0 26px;
color: #fff;
font-size: 14px;
letter-spacing: 1px;
box-shadow: 0 4px 4px 0 rgba(255, 255, 255, 0.25) inset;
}
.floating-button .custom-menu-box>div {
line-height: 42px;
position: relative;
cursor: pointer;
}
.floating-button .custom-menu-box>div::after {
position: absolute;
display: block;
content: '';
width: 100%;
height: 1px;
background: linear-gradient(to right, rgba(255, 255, 255,
0.2), rgba(255, 255, 255, 0));
top: 100%;
left: 0;
}
.floating-button .custom-menu-box>.custom-last-div::after {
background: transparent;
}
.floating-button .custom-menu-box .operate-L1Arr-open {
width: 24px;
height: 24px;
position: absolute;
right: 6px;
top: 9px;
background-image: url('../../assets/images/ball/gengduo1 2@2x.png');
/* 图标路径 */
background-size: cover;
/* 图标适应尺寸 */
background-repeat: no-repeat;
/* 禁止重复 */
}