首次提交:脑晴测 H5 项目
包含前端源码、预构建产物(dist/)、零依赖 Node.js 服务器(server.mjs)、 Cloudflare Worker 版本(worker/)、部署文档(DEPLOY.md)和交接文档。 技术总监可直接 clone 后运行 node server.mjs 启动,无需安装 React 或 build。 DEEPSEEK_API_KEY 通过环境变量注入,未纳入仓库。
This commit is contained in:
69
brain-h5/src/mobile/MobileCursor.tsx
Normal file
69
brain-h5/src/mobile/MobileCursor.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { useState, type PointerEvent as ReactPointerEvent } from "react";
|
||||
|
||||
type CursorState = {
|
||||
visible: boolean;
|
||||
active: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export function useMobileCursor() {
|
||||
const debug =
|
||||
typeof window !== "undefined" && new URLSearchParams(window.location.search).has("cursorDebug");
|
||||
const [cursor, setCursor] = useState<CursorState>({
|
||||
visible: false,
|
||||
active: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const updatePosition = (event: ReactPointerEvent<HTMLElement>) => {
|
||||
const bounds = event.currentTarget.getBoundingClientRect();
|
||||
const localX = bounds.width === 0 ? 0 : ((event.clientX - bounds.left) / bounds.width) * event.currentTarget.offsetWidth;
|
||||
const localY =
|
||||
bounds.height === 0 ? 0 : ((event.clientY - bounds.top) / bounds.height) * event.currentTarget.offsetHeight;
|
||||
|
||||
setCursor((current) => ({
|
||||
...current,
|
||||
visible: true,
|
||||
x: localX,
|
||||
y: localY,
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
cursorHandlers: {
|
||||
onPointerEnter: updatePosition,
|
||||
onPointerMove: updatePosition,
|
||||
onPointerDown: (event: ReactPointerEvent<HTMLElement>) => {
|
||||
updatePosition(event);
|
||||
setCursor((current) => ({ ...current, active: true }));
|
||||
},
|
||||
onPointerUp: (event: ReactPointerEvent<HTMLElement>) => {
|
||||
updatePosition(event);
|
||||
setCursor((current) => ({ ...current, active: false }));
|
||||
},
|
||||
onPointerCancel: () => {
|
||||
setCursor((current) => ({ ...current, active: false, visible: false }));
|
||||
},
|
||||
onPointerLeave: () => {
|
||||
setCursor((current) => ({ ...current, active: false, visible: false }));
|
||||
},
|
||||
},
|
||||
cursorDebug: debug,
|
||||
cursorElement: (
|
||||
<div
|
||||
className="mobile-cursor"
|
||||
data-active={cursor.active ? "true" : "false"}
|
||||
data-debug={debug ? "true" : "false"}
|
||||
data-visible={cursor.visible ? "true" : "false"}
|
||||
data-testid="mobile-cursor"
|
||||
style={{
|
||||
transform: `translate3d(${cursor.x}px, ${cursor.y}px, 0) translate(-50%, -50%)`,
|
||||
}}
|
||||
>
|
||||
{debug ? <span className="mobile-cursor-hotspot" aria-hidden="true" /> : null}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user