139 lines
4.0 KiB
TypeScript
139 lines
4.0 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import type { RefObject } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import type { Neighbors } from "../types.ts";
|
|
|
|
interface UseNavigationProps {
|
|
neighbors: Neighbors;
|
|
isVideo: boolean;
|
|
mediaContainerRef: RefObject<HTMLDivElement | null>;
|
|
videoRef?: RefObject<HTMLVideoElement | null>;
|
|
prevImg?: () => void;
|
|
nextImg?: () => void;
|
|
togglePlay: () => void;
|
|
}
|
|
|
|
export function useNavigation({
|
|
neighbors,
|
|
isVideo,
|
|
mediaContainerRef,
|
|
videoRef,
|
|
prevImg,
|
|
nextImg,
|
|
togglePlay,
|
|
}: UseNavigationProps) {
|
|
const router = useRouter();
|
|
const wheelCooldownRef = useRef<number>(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) => {
|
|
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 as any);
|
|
}, [neighbors, mediaContainerRef, router]);
|
|
|
|
// 键盘快捷键
|
|
useEffect(() => {
|
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
const target = e.target as HTMLElement;
|
|
if (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]);
|
|
|
|
// 浏览器返回事件
|
|
useEffect(() => {
|
|
window.history.pushState({ interceptBack: true }, "");
|
|
|
|
const handlePopState = () => {
|
|
window.close();
|
|
setTimeout(() => {
|
|
if (!document.hidden) {
|
|
router.push("/");
|
|
}
|
|
}, 100);
|
|
};
|
|
|
|
window.addEventListener("popstate", handlePopState);
|
|
return () => {
|
|
window.removeEventListener("popstate", handlePopState);
|
|
};
|
|
}, [router]);
|
|
}
|