| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- import { useState, useRef } from 'react';
- import Draggable, { type DraggableEvent, type DraggableData } from 'react-draggable';
- import { Gamepad2 } from 'lucide-react';
- interface FloatingButtonProps {
- onClick: () => void;
- }
- export function FloatingButton({ onClick }: FloatingButtonProps) {
- const [position, setPosition] = useState({ x: 20, y: 100 });
- const [isDragging, setIsDragging] = useState(false);
- const [isIdle, setIsIdle] = useState(false);
- const nodeRef = useRef<HTMLDivElement>(null);
-
- const idleTimerRef = useRef<any>(null);
- const dragRef = useRef(false); // Track real drag movement
- // 清除空闲倒计时
- const clearIdleTimer = () => {
- if (idleTimerRef.current) {
- clearTimeout(idleTimerRef.current);
- idleTimerRef.current = null;
- }
- setIsIdle(false);
- };
- const handleDragStart = () => {
- dragRef.current = false;
- clearIdleTimer();
- setIsDragging(true);
-
- // 如果处于空闲状态(半隐藏),视觉上立刻恢复原状
- if (isIdle) {
- setPosition(prev => {
- const btnWidth = nodeRef.current ? nodeRef.current.offsetWidth : 48;
- return {
- ...prev,
- x: prev.x < 0 ? 0 : window.innerWidth - btnWidth
- };
- });
- }
- };
- const handleDrag = (_e: DraggableEvent, data: DraggableData) => {
- dragRef.current = true;
- setPosition({ x: data.x, y: data.y });
- };
- const handleDragStop = (_e: DraggableEvent, data: DraggableData) => {
- setIsDragging(false);
- if (!dragRef.current) {
- onClick();
- }
- // 自动吸附屏幕左右边缘
- const btnWidth = nodeRef.current ? nodeRef.current.offsetWidth : 48; // 默认 48px
- const btnHeight = nodeRef.current ? nodeRef.current.offsetHeight : 48;
- // 限制不要拖出上下边缘
- let endY = data.y;
- const maxY = window.innerHeight - btnHeight;
- endY = Math.max(0, Math.min(endY, maxY));
-
- let endX = data.x;
- const centerX = data.x + (btnWidth / 2);
- const windowCenterX = window.innerWidth / 2;
- endX = centerX < windowCenterX ? 0 : window.innerWidth - btnWidth;
-
- setPosition({ x: endX, y: endY });
- // 启动 3 秒空闲隐藏计时器
- idleTimerRef.current = setTimeout(() => {
- setIsIdle(true);
- // 将半个按钮宽度移出屏幕之外
- setPosition(prev => {
- const halfWidth = btnWidth / 2;
- const newX = prev.x < windowCenterX ? -halfWidth : window.innerWidth - halfWidth;
- return { ...prev, x: newX };
- });
- }, 3000);
- };
- return (
- <Draggable
- nodeRef={nodeRef}
- position={position}
- onStart={handleDragStart}
- onDrag={handleDrag}
- onStop={handleDragStop}
- >
- <div
- ref={nodeRef}
- className={`fixed top-0 left-0 z-50 flex items-center justify-center w-12 h-12 bg-blue-600 rounded-full shadow-lg cursor-move active:scale-95
- ${isDragging ? 'opacity-80 scale-105 shadow-xl transition-none' : 'opacity-100 hover:scale-110 transition-all duration-300 ease-out'}
- ${isIdle ? 'opacity-50' : ''}`}
- style={{ touchAction: 'none' }}
- >
- <Gamepad2 className="text-white w-6 h-6 pointer-events-none" />
- </div>
- </Draggable>
- );
- }
|