likes
comments
collection
share

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

作者站长头像
站长
· 阅读数 23

深度解析 React 可拖动悬浮球

老板:我想要一个 web 悬浮球,能实现吗

我:ok

老板:我想要一个像 mac 一样的菜单栏

我:ok

小朋友才做选择,两个都安排上。

结果就是,普通的应用栏有悬浮球也有,右键切换,如下图。

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

本文将介绍一个 React 实现的高级可拖动悬浮球组件,它不仅具备基本功能,还集成了多项交互特性,大大提升了用户体验。

组件概览

这个悬浮球组件主要具有以下特点:

  1. 可拖拽定位
  2. 圆形应用菜单
  3. 边缘吸附功能
  4. 右键上下文菜单
  5. 应用快速切换

可拖拽功能

使用 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 });

边界计算

首先,我们需要获取浏览器窗口和悬浮球的尺寸信息:

const browserWidth = window.innerWidth;
const browserHeight = window.innerHeight;
const floatButtonNav = document?.getElementById('floatButtonNav');
if (!floatButtonNav) return;

const distanceLeft = floatButtonNav.getBoundingClientRect().left;
const floatButtonNavWidth = floatButtonNav.clientHeight || 64;
const floatButtonNavHeight = floatButtonNav.clientHeight || 64;

接下来,我们计算拖拽的边界值:

// 120 is absolute positioning; 10 Boundary distance;
const leftBoundary = -browserWidth + floatButtonNavWidth + 120 + 10;
const rightBoundary = 120 - 10;
const topBoundary = -browserHeight + floatButtonNavHeight + 120;
const bottomBoundary = 120;

120 是悬浮球初始定位,10是额外的间距

位置限制

然后,我们使用这些边界值来限制悬浮球的位置:

setPosition({
  x: x < leftBoundary ? leftBoundary : x > rightBoundary ? rightBoundary : x,
  y: y < topBoundary ? topBoundary : y > bottomBoundary ? bottomBoundary : y
});

边缘吸附效果

新增一个边缘吸附的状态,根据不同的状态,展示出吸附浏览器左右两边的效果

主要是一些动画的效果

const handleSuction = (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: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
  display: flex;
  justify-content: center;
  align-items: center;
  transition: all 0.5s ease;

  &[data-suction='0'] {
    opacity: 1;
  }

  &[data-suction='left'] {
    transform: translateX(-100px);
    animation: slide-right-left 300ms cubic-bezier(0.55, 0.085, 0.68, 0.53);
  }

  &[data-suction='right'] {
    transform: translateX(100px);
    animation: slide-left-right 300ms cubic-bezier(0.55, 0.085, 0.68, 0.53);
  }
}

@keyframes slide-right-left {
  0% {
    transform: translateX(40px);
  }
  100% {
    transform: translateX(-100px);
    opacity: 0;
  }
}

@keyframes slide-left-right {
  0% {
    transform: translateX(-40px);
  }
  100% {
    transform: translateX(100px);
    opacity: 0;
  }
}

圆形应用菜单(悬浮球)

通过对矩形元素进行变换来创建扇形,进而组成圆形菜单。

效果

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

实现原理

  1. 一个圆形,被均匀分割成6-8个扇形。每个扇形代表一个菜单项。中心有一个小圆,代表悬浮球本身
  2. 单个扇形的角度计算
    • 整个圆的360度
    • 扇形的角度(degree)= 360 / 菜单项数量
    • 用箭头指示扇形的旋转方向
  3. 矩形到扇形的变换过程
    • 开始时是一个矩形
    • 矩形旋转(rotate)
    • 矩形倾斜(skew),形成扇形
const [degree, contentSkewDegree, contentRotateDegree] = useMemo(() => {
  const len = apps?.length < 6 ? 6 : apps?.length > 8 ? 8 : apps?.length;
  const temp: number = 360 / len;
  const skewDegree = -(90 - temp);
  const rotateDegree = -(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>
  1. 有了扇形之后,还需要一个盒子放内容,想象一下,如果我们能看到这个过程的动画:
    • 首先,我们有一个倾斜的扇形。
    • 然后,内容容器反向倾斜,"站直"了。
    • 最后,容器稍微旋转,使得内容正对圆心。
transform={`skew(${contentSkewDegree}deg) rotate(${contentRotateDegree}deg)`}

这行代码是整个容器的精髓所在。它包含两个关键的变换:

  • skew(${contentSkewDegree}deg): 这个倾斜变换是为了抵消父元素(扇形)的倾斜效果。还记得我们如何通过倾斜矩形来创建扇形吗?这里我们做的是反向操作,目的是让内容回到水平状态。
  • rotate(${contentRotateDegree}deg): 旋转变换确保内容面向圆心。没有这个旋转,图标可能会歪歪扭扭,不能很好地展示。
<Flex
  justifyContent={'center'}
  pt="12px"
  className={styles.subItem}
  // The icon is perpendicular to the center of the circle
  transform={`skew(${contentSkewDegree}deg) rotate(${contentRotateDegree}deg)`}
>

深度解析 React 可拖动悬浮球本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂

  1. 最后小调整一下图标,使其垂直向下
  const calculateDegree = (index: number) => {
    const temp = -(degree * index + contentRotateDegree);
    return `rotate(${temp}deg)`;
  };
<Flex
  w="32px"
  h="32px"
  backgroundColor={'rgba(244, 246, 248, 0.9)'}
  border={'1px solid #FFFFFF'}
  borderRadius={'50%'}
  boxShadow={'0px 0.5px 1px rgba(0, 0, 0, 0.2)'}
  justifyContent={'center'}
  alignItems={'center'}
  // The icon is perpendicular to the x-axis of the page
  transform={calculateDegree(index + 1)}
>
icon
</Flex>

右键菜单

使用 react-contexify 实现右键菜单:

const { show } = useContextMenu({
  id: Floating_Button_Menu_Id
});

const displayMenu = (e: MouseEvent<HTMLDivElement>) => {
  e.stopPropagation();
  setIsRightClick(true);
  onClose();
  show({
    event: e,
    position: {
      x: '-65%',
      y: '-80%'
    }
  });
};

应用切换

点击应用图标切换应用:

const handleNavItem = (e: MouseEvent<HTMLDivElement>, item: AppInfo) => {
  if (item.key === 'system-home') {
    // 处理主页逻辑
  } else if (item.pid === currentAppPid && item.size !== 'minimize') {
    // 最小化当前应用
  } else {
    // 切换到选中应用
    switchAppById(item.pid);
  }
};

总结

本文深入剖析了一个基于 React 实现的高级可拖动悬浮球组件。这个组件集成了多项复杂的交互功能。

我们详细分析了以下核心方面:

  1. 拖拽功能实现
    • 使用 react-draggable 实现基础拖拽。
    • 通过边界计算确保悬浮球始终在可视区域内。
    • 实现了边缘吸附效果,增强用户体验。
  2. 圆形菜单的巧妙设计
    • 利用 CSS 变换将矩形元素转化为扇形,组成圆形菜单。
    • 通过角度计算,确保菜单项均匀分布。
    • 实现内容的精确对齐,使图标始终面向圆心。
  3. 状态管理和性能优化
    • 使用 React Hooks 管理组件的多个状态。
    • 利用 useMemo 优化计算密集型操作。
  4. 用户交互增强
    • 实现右键菜单功能。
    • 添加悬停效果和动态样式变化。
转载自:https://juejin.cn/post/7407334387932463145
评论
请登录