init
This commit is contained in:
42
src/component/floatingMenu/index.less
Normal file
42
src/component/floatingMenu/index.less
Normal file
@@ -0,0 +1,42 @@
|
||||
/* FloatingFanMenu.css */
|
||||
|
||||
@keyframes menuItemPop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0) rotate(-180deg);
|
||||
}
|
||||
70% {
|
||||
opacity: 1;
|
||||
transform: scale(1.1) rotate(-10deg);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 悬停效果 */
|
||||
.menu-item:hover {
|
||||
transform: scale(1.1) !important;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3) !important;
|
||||
transition: all 0.2s ease !important;
|
||||
}
|
||||
.adm-floating-bubble-button {
|
||||
z-index: 999;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
overflow: visible;
|
||||
.cat {
|
||||
position: absolute;
|
||||
width: 70px;
|
||||
font-size: 12px;
|
||||
|
||||
bottom: -10px;
|
||||
background: rgba(255, 204, 199, 1);
|
||||
padding: 4px 0px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
196
src/component/floatingMenu/index.tsx
Normal file
196
src/component/floatingMenu/index.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import { FloatingBubble, Image } from "antd-mobile";
|
||||
import {
|
||||
AddOutline,
|
||||
MessageOutline,
|
||||
UserOutline,
|
||||
SetOutline,
|
||||
HeartOutline,
|
||||
CheckOutline,
|
||||
} from "antd-mobile-icons";
|
||||
import { createPortal } from "react-dom";
|
||||
import dogSvg from "@/assets/translate/dog.svg";
|
||||
import catSvg from "@/assets/translate/cat.svg";
|
||||
import pigSvg from "@/assets/translate/pig.svg";
|
||||
import "./index.less";
|
||||
import { MoreTwo } from "@icon-park/react";
|
||||
|
||||
export interface FloatMenuItemConfig {
|
||||
icon: React.ReactNode;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const FloatingFanMenu: React.FC<{
|
||||
menuItems: FloatMenuItemConfig[];
|
||||
value?: FloatMenuItemConfig;
|
||||
onChange?: (item: FloatMenuItemConfig) => void;
|
||||
}> = (props) => {
|
||||
const { menuItems = [] } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
|
||||
const bubbleRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 点击时获取FloatingBubble的位置
|
||||
const handleMainClick = () => {
|
||||
if (!visible) {
|
||||
// 显示菜单时,获取当前FloatingBubble的位置
|
||||
if (bubbleRef.current) {
|
||||
const bubble = bubbleRef.current.querySelector(
|
||||
".adm-floating-bubble-button"
|
||||
);
|
||||
if (bubble) {
|
||||
const rect = bubble.getBoundingClientRect();
|
||||
setMenuPosition({
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.top + rect.height / 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
setVisible(!visible);
|
||||
};
|
||||
|
||||
const handleItemClick = (item: FloatMenuItemConfig) => {
|
||||
props.onChange?.(item);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 计算菜单项位置
|
||||
const getMenuItemPosition = (index: number) => {
|
||||
const positions = [
|
||||
{ x: 0, y: -80 }, // 上方
|
||||
{ x: -60, y: -60 }, // 左上
|
||||
{ x: -80, y: 0 }, // 左方
|
||||
{ x: -60, y: 60 }, // 左下
|
||||
];
|
||||
|
||||
const pos = positions[index] || { x: 0, y: 0 };
|
||||
let x = menuPosition.x + pos.x;
|
||||
let y = menuPosition.y + pos.y;
|
||||
|
||||
// // 边界检测
|
||||
// const itemSize = 48;
|
||||
// const margin = 20;
|
||||
// 边界检测
|
||||
const itemSize = 48;
|
||||
// const margin = 0;
|
||||
|
||||
// x = Math.max(
|
||||
// // margin + itemSize / 2,
|
||||
// Math.min(window.innerWidth - margin - itemSize / 2, x)
|
||||
// );
|
||||
// y = Math.max(
|
||||
// // margin + itemSize / 2,
|
||||
// Math.min(window.innerHeight - margin - itemSize / 2, y)
|
||||
// );
|
||||
|
||||
return {
|
||||
left: x - itemSize / 2,
|
||||
top: y - itemSize / 2,
|
||||
};
|
||||
};
|
||||
|
||||
// 菜单组件
|
||||
const MenuComponent = () => (
|
||||
<>
|
||||
{/* 背景遮罩 */}
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||||
zIndex: 9998,
|
||||
}}
|
||||
onClick={() => setVisible(false)}
|
||||
/>
|
||||
|
||||
{/* 菜单项 */}
|
||||
{menuItems.map((item, index) => {
|
||||
const position = getMenuItemPosition(index);
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
position: "fixed",
|
||||
...position,
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "#fff",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "white",
|
||||
fontSize: 20,
|
||||
cursor: "pointer",
|
||||
zIndex: 9999,
|
||||
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.2)",
|
||||
animation: `menuItemPop 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards`,
|
||||
animationDelay: `${index * 0.1}s`,
|
||||
opacity: 0,
|
||||
transform: "scale(0)",
|
||||
}}
|
||||
onClick={() => handleItemClick(item)}
|
||||
// title={item.label}
|
||||
>
|
||||
{item.icon}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 主按钮 */}
|
||||
<div ref={bubbleRef}>
|
||||
<FloatingBubble
|
||||
style={{
|
||||
"--initial-position-bottom": "200px",
|
||||
"--initial-position-right": "24px",
|
||||
"--edge-distance": "24px",
|
||||
}}
|
||||
onClick={handleMainClick}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: 24,
|
||||
color: "white",
|
||||
background: visible
|
||||
? "linear-gradient(135deg, #ff4d4f, #ff7875)"
|
||||
: "linear-gradient(135deg, #fff, #fff)",
|
||||
borderRadius: "50%",
|
||||
transition: "all 0.3s ease",
|
||||
transform: visible ? "rotate(45deg)" : "rotate(0deg)",
|
||||
boxShadow: visible
|
||||
? "0 6px 16px rgba(255, 77, 79, 0.4)"
|
||||
: "0 4px 12px #eee",
|
||||
}}
|
||||
>
|
||||
{props.value?.icon}
|
||||
<div className="cat">
|
||||
{/* {!visible && <CheckOutline style={{ marginRight: "3px" }} />} */}
|
||||
切换语言
|
||||
</div>
|
||||
</div>
|
||||
</FloatingBubble>
|
||||
</div>
|
||||
|
||||
{/* 菜单 - 只在有位置信息时渲染 */}
|
||||
{visible &&
|
||||
menuPosition.x > 0 &&
|
||||
menuPosition.y > 0 &&
|
||||
createPortal(<MenuComponent />, document.body)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FloatingFanMenu;
|
||||
Reference in New Issue
Block a user