import { useRouter } from "next/navigation"; import type { RefObject } from "react"; import { useEffect, useRef } from "react"; import type { Neighbors } from "../types.ts"; interface UseNavigationProps { neighbors: Neighbors; isVideo: boolean; mediaContainerRef: RefObject; videoRef?: RefObject; prevImg?: () => void; nextImg?: () => void; togglePlay: () => void; } export function useNavigation({ neighbors, isVideo, mediaContainerRef, videoRef, prevImg, nextImg, togglePlay, }: UseNavigationProps): void { const router = useRouter(); const wheelCooldownRef = useRef(0); // 预取路由 useEffect(() => { if (!neighbors) return; if (neighbors.next) router.prefetch(`/aweme/${neighbors.next.aweme_id}`); if (neighbors.prev) router.prefetch(`/aweme/${neighbors.prev.aweme_id}`); }, [neighbors, router]); // 鼠标滚轮切换 useEffect(() => { const el = mediaContainerRef.current; if (!el) return; const onWheel = (e: WheelEvent): void => { if (e.ctrlKey) return; const now = performance.now(); if (now - wheelCooldownRef.current < 700) return; const dy = e.deltaY; if (Math.abs(dy) < 40) return; if ((dy > 0 && neighbors?.next) || (dy < 0 && neighbors?.prev)) { e.preventDefault(); } if (dy > 0 && neighbors?.next) { wheelCooldownRef.current = now; router.push(`/aweme/${neighbors.next.aweme_id}`); } else if (dy < 0 && neighbors?.prev) { wheelCooldownRef.current = now; router.push(`/aweme/${neighbors.prev.aweme_id}`); } }; el.addEventListener("wheel", onWheel, { passive: false }); return () => el.removeEventListener("wheel", onWheel); }, [neighbors, mediaContainerRef, router]); // 键盘快捷键 useEffect(() => { const onKeyDown = (e: KeyboardEvent): void => { const target = e.target as HTMLElement | null; if ( target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) ) { return; } const key = e.key.toLowerCase(); if (key === "arrowup" || key === "w") { e.preventDefault(); const now = performance.now(); if (now - wheelCooldownRef.current < 700) return; if (neighbors?.prev) { wheelCooldownRef.current = now; router.push(`/aweme/${neighbors.prev.aweme_id}`); } } else if (key === "arrowdown" || key === "s") { e.preventDefault(); const now = performance.now(); if (now - wheelCooldownRef.current < 700) return; if (neighbors?.next) { wheelCooldownRef.current = now; router.push(`/aweme/${neighbors.next.aweme_id}`); } } else if (key === "arrowleft" || key === "a") { e.preventDefault(); if (isVideo) { const v = videoRef?.current; if (v && v.duration) { v.currentTime = Math.max(0, v.currentTime - 5); } } else { prevImg?.(); } } else if (key === "arrowright" || key === "d") { e.preventDefault(); if (isVideo) { const v = videoRef?.current; if (v && v.duration) { v.currentTime = Math.min(v.duration, v.currentTime + 5); } } else { nextImg?.(); } } else if (key === " ") { e.preventDefault(); togglePlay(); } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [isVideo, neighbors, router, videoRef, prevImg, nextImg, togglePlay]); // 仅在无历史记录的独立新标签页首次进入时注入一次返回兜底,避免连续刷视频时无限叠加 history 栈 useEffect(() => { const currentState = window.history.state as | { interceptBack?: boolean } | null | undefined; if (window.history.length <= 1 && !currentState?.interceptBack) { window.history.pushState({ ...currentState, interceptBack: true }, ""); } const handlePopState = (): void => { if (window.history.length <= 1) { window.close(); setTimeout(() => { if (!document.hidden) { router.push("/"); } }, 100); } }; window.addEventListener("popstate", handlePopState); return () => { window.removeEventListener("popstate", handlePopState); }; }, [router]); }