深度解析 React 可拖动悬浮球
点击关注公众号,“技术干货”及时达!深度解析 React 可拖动悬浮球老板:我想要一个 web 悬浮球,能实现吗
我:ok
老板:我想要一个像 mac 一样的菜单栏
我:ok
小朋友才做选择,两个都安排上。
结果就是,普通的应用栏有悬浮球也有,右键切换,如下图。
本文将介绍一个 React 实现的高级可拖动悬浮球组件,它不仅具备基本功能,还集成了多项交互特性,大大提升了用户体验。
组件概览这个悬浮球组件主要具有以下特点:
可拖拽定位圆形应用菜单边缘吸附功能右键上下文菜单应用快速切换可拖拽功能使用 react-draggable 库实现拖拽功能:
Draggable
onStart={(e,position)={
setDragging(true);
setStartPosition(position);
}}
onDrag={(e,position)={
setPosition(position);
}}
onStop={(e,position)={
handleDragBoundary(e,position);
setEndPosition(position);
setDragging(false);
}}
handle="#centerButton"
position={position}
{/*悬浮球内容*/}
/Draggable
这里通过 onStart、onDrag 和 onStop 事件来管理拖拽状态和位置。
拖动是通过 transform: translate(110px, -260px); 实现的用x,y记录拖动的位置 const [position, setPosition] = useState({ x: 0, y: 0 });
边界计算首先,我们需要获取浏览器窗口和悬浮球的尺寸信息:
constbrowserWidth=window.innerWidth;
constbrowserHeight=window.innerHeight;
constfloatButtonNav=document?.getElementById('floatButtonNav');
if(!floatButtonNav)return;
constdistanceLeft=floatButtonNav.getBoundingClientRect().left;
constfloatButtonNavWidth=floatButtonNav.clientHeight||64;
constfloatButtonNavHeight=floatButtonNav.clientHeight||64;
接下来,我们计算拖拽的边界值:
//120isabsolutepositioning;10Boundarydistance;
constleftBoundary=-browserWidth+floatButtonNavWidth+120+10;
constrightBoundary=120-10;
consttopBoundary=-browserHeight+floatButtonNavHeight+120;
constbottomBoundary=120;
120 是悬浮球初始定位,10是额外的间距
位置限制然后,我们使用这些边界值来限制悬浮球的位置:
setPosition({
x:xleftBoundary?leftBoundary:xrightBoundary?rightBoundary:x,
y:ytopBoundary?topBoundary:ybottomBoundary?bottomBoundary:y
});
边缘吸附效果新增一个边缘吸附的状态,根据不同的状态,展示出吸附浏览器左右两边的效果
主要是一些动画的效果
consthandleSuction=(suction:Suction)={
onClose();
setSuction(suction);
setTimeout(()={
setLockSuction(false);
},1000);
};
xleftBoundary
?handleSuction(Suction.Left)
:xrightBoundary
?handleSuction(Suction.Right)
:null;
.floatBtn{
will-change:auto;
position:absolute;
z-index:9999;
width:64px;
height:64px;
border-radius:50%;
background:rgba(28,32,35,0.9);
box-shadow:0px0px0px1pxrgba(0,0,0,0.1);
display:flex;
justify-content:center;
align-items:center;
transition:all0.5sease;
&[data-suction='0']{
opacity:1;
}
&[data-suction='left']{
transform:translateX(-100px);
animation:slide-right-left300mscubic-bezier(0.55,0.085,0.68,0.53);
}
&[data-suction='right']{
transform:translateX(100px);
animation:slide-left-right300mscubic-bezier(0.55,0.085,0.68,0.53);
}
}
@keyframesslide-right-left{
0%{
transform:translateX(40px);
}
100%{
transform:translateX(-100px);
opacity:0;
}
}
@keyframesslide-left-right{
0%{
transform:translateX(-40px);
}
100%{
transform:translateX(100px);
opacity:0;
}
}
圆形应用菜单(悬浮球)通过对矩形元素进行变换来创建扇形,进而组成圆形菜单。
效果
实现原理一个圆形,被均匀分割成6-8个扇形。每个扇形代表一个菜单项。中心有一个小圆,代表悬浮球本身单个扇形的角度计算整个圆的360度扇形的角度(degree)= 360 / 菜单项数量用箭头指示扇形的旋转方向矩形到扇形的变换过程开始时是一个矩形矩形旋转(rotate)矩形倾斜(skew),形成扇形const[degree,contentSkewDegree,contentRotateDegree]=useMemo(()={
constlen=apps?.length6?6:apps?.length8?8:apps?.length;
consttemp:number=360/
constskewDegree=-(90-temp);
constrotateDegree=-(90-temp/2);
return[temp,skewDegree,rotateDegree];
},[apps.length]);
Box
transform={
isOpen
?`rotate(${degree*(index+1)}deg)skew(${90-degree}deg)`
:`rotate(75deg)skew(60deg)`
}
//...其他属性
{/*内容*/}
/Box
有了扇形之后,还需要一个盒子放内容,想象一下,如果我们能看到这个过程的动画:首先,我们有一个倾斜的扇形。然后,内容容器反向倾斜,"站直"了。最后,容器稍微旋转,使得内容正对圆心。transform={`skew(${contentSkewDegree}deg)rotate(${contentRotateDegree}deg)`}
这行代码是整个容器的精髓所在。它包含两个关键的变换:
「skew(${contentSkewDegree}deg)」: 这个倾斜变换是为了抵消父元素(扇形)的倾斜效果。还记得我们如何通过倾斜矩形来创建扇形吗?这里我们做的是反向操作,目的是让内容回到水平状态。「rotate(${contentRotateDegree}deg)」: 旋转变换确保内容面向圆心。没有这个旋转,图标可能会歪歪扭扭,不能很好地展示。Flex
justifyContent={'center'}
pt="12px"
className={styles.subItem}
//Theiconisperpendiculartothecenterofthecircle
transform={`skew(${contentSkewDegree}deg)rotate(${contentRotateDegree}deg)`}
最后小调整一下图标,使其垂直向下constcalculateDegree=(index:number)={
consttemp=-(degree*index+contentRotateDegree);
return`rotate(${temp}deg)`;
Flex
w="32px"
h="32px"
backgroundColor={'rgba(244,246,248,0.9)'}
border={'1pxsolid#FFFFFF'}
borderRadius={'50%'}
boxShadow={'0px0.5px1pxrgba(0,0,0,0.2)'}
justifyContent={'center'}
alignItems={'center'}
//Theiconisperpendiculartothex-axisofthepage
transform={calculateDegree(index+1)}
icon
/Flex
右键菜单使用 react-contexify 实现右键菜单:
const{show}=useContextMenu({
id:Floating_Button_Menu_Id
});
constdisplayMenu=(e:MouseEventHTMLDivElement)={
e.stopPropagation();
setIsRightClick(true);
onClose();
show({
event:e,
position:{
x:'-65%',
y:'-80%'
}
};
应用切换点击应用图标切换应用:
consthandleNavItem=(e:MouseEventHTMLDivElement,item:AppInfo)={
if(item.key==='system-home'){
//处理主页逻辑
}elseif(item.pid===currentAppPiditem.size!=='minimize'){
//最小化当前应用
}else{
//切换到选中应用
switchAppById(item.pid);
}
};
总结本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂的交互功能。
我们详细分析了以下核心方面:
「拖拽功能实现」:使用 react-draggable 实现基础拖拽。通过边界计算确保悬浮球始终在可视区域内。实现了边缘吸附效果,增强用户体验。「圆形菜单的巧妙设计」:利用 CSS 变换将矩形元素转化为扇形,组成圆形菜单。通过角度计算,确保菜单项均匀分布。实现内容的精确对齐,使图标始终面向圆心。「状态管理和性能优化」:使用 React Hooks 管理组件的多个状态。利用 useMemo 优化计算密集型操作。「用户交互增强」:实现右键菜单功能。添加悬停效果和动态样式变化。
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线