包阅导读总结
1.
关键词:`React`、`可拖动悬浮球`、`圆形菜单`、`右键菜单`、`应用切换`
2.
总结:本文介绍了基于 React 实现的高级可拖动悬浮球组件,包括其拖拽功能的实现、边界计算、边缘吸附效果、圆形应用菜单的创建原理、右键菜单的实现以及应用切换逻辑,还使用了多种技术和库,集成了复杂交互功能。
3.
主要内容:
– 可拖动悬浮球组件
– 利用 `react-draggable` 库实现拖拽功能,通过事件管理拖拽状态和位置,通过 `transform` 实现拖动效果,用 `x`、`y` 记录位置。
– 计算浏览器窗口和悬浮球的尺寸,得出拖拽边界值以限制悬浮球位置。
– 新增边缘吸附状态,实现吸附浏览器左右两边的效果及相应动画。
– 圆形应用菜单
– 通过对矩形元素变换创建扇形组成圆形菜单,计算每个扇形角度。
– 用特定代码实现容器变换,确保内容水平和面向圆心。
– 其他功能
– 右键菜单使用 `react-contexify` 实现。
– 点击应用图标可切换应用。
– 总结该组件集成了多种复杂交互功能,并使用了 `React Hooks` 管理多个状态。
思维导图:
文章地址:https://mp.weixin.qq.com/s/NulcPdszinHOqfJPlU76JQ
文章来源:mp.weixin.qq.com
作者:cv也要开心
发布时间:2024/8/31 8:23
语言:中文
总字数:1369字
预计阅读时间:6分钟
评分:91分
标签:React,前端开发,拖拽功能,边缘吸附,右键菜单
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
点击关注公众号,“技术干货”及时达!
深度解析 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:x<leftBoundary?leftBoundary:x>rightBoundary?rightBoundary:x,
y:y<topBoundary?topBoundary:y>bottomBoundary?bottomBoundary:y
});
边缘吸附效果
新增一个边缘吸附的状态,根据不同的状态,展示出吸附浏览器左右两边的效果
主要是一些动画的效果
consthandleSuction=(suction:Suction)=>{
onClose();
setSuction(suction);
setTimeout(()=>{
setLockSuction(false);
},1000);
};
x<leftBoundary
?handleSuction(Suction.Left)
:x>rightBoundary
?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个扇形。每个扇形代表一个菜单项。中心有一个小圆,代表悬浮球本身
-
扇形的角度(degree)= 360 / 菜单项数量
const[degree,contentSkewDegree,contentRotateDegree]=useMemo(()=>{
constlen=apps?.length<6?6:apps?.length>8?8:apps?.length;
consttemp:number=360/len;
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:MouseEvent<HTMLDivElement>)=>{
e.stopPropagation();
setIsRightClick(true);
onClose();
show({
event:e,
position:{
x:'-65%',
y:'-80%'
}
});
};
应用切换
点击应用图标切换应用:
consthandleNavItem=(e:MouseEvent<HTMLDivElement>,item:AppInfo)=>{
if(item.key==='system-home'){
//处理主页逻辑
}elseif(item.pid===currentAppPid&&item.size!=='minimize'){
//最小化当前应用
}else{
//切换到选中应用
switchAppById(item.pid);
}
};
总结
本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂的交互功能。
我们详细分析了以下核心方面:
-
使用 react-draggable 实现基础拖拽。
-
利用 CSS 变换将矩形元素转化为扇形,组成圆形菜单。
-
使用 React Hooks 管理组件的多个状态。