refactor: 重构作品播放页性能、媒体同步与内存管理

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-04 01:31:10 +08:00
co-authored by Cursor
parent 990e1b1804
commit 10f2520279
19 changed files with 1351 additions and 813 deletions
@@ -1,74 +1,90 @@
import { useEffect } from "react";
import type { RefObject } from "react";
import { useEffect } from "react";
interface UseBackgroundCanvasProps {
isVideo: boolean;
idx: number;
isPlaying: boolean;
videoRef: RefObject<HTMLVideoElement | null>;
scrollerRef: RefObject<HTMLDivElement | null>;
backgroundCanvasRef: RefObject<HTMLCanvasElement | null>;
}
const CANVAS_DRAW_INTERVAL_MS = 100; // 10fps 对 1/10 分辨率 + blur(40px) 已完全平滑,大幅降低 GPU 负载
export function useBackgroundCanvas({
isVideo,
idx,
isPlaying,
videoRef,
scrollerRef,
backgroundCanvasRef,
}: UseBackgroundCanvasProps) {
}: UseBackgroundCanvasProps): void {
// 初始化尺寸与卸载清理
useEffect(() => {
const canvas = backgroundCanvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
if (!isVideo) {
canvas.width = 1;
canvas.height = 1;
ctx.clearRect(0, 0, 1, 1);
if (!ctx || !isVideo) {
canvas.width = 0;
canvas.height = 0;
return;
}
const updateCanvasSize = () => {
canvas.width = Math.floor(window.innerWidth / 10);
canvas.height = Math.floor(window.innerHeight / 10);
const updateCanvasSize = (): void => {
canvas.width = Math.max(1, Math.floor(window.innerWidth / 10));
canvas.height = Math.max(1, Math.floor(window.innerHeight / 10));
};
updateCanvasSize();
let resizeTimeout: NodeJS.Timeout;
const debouncedResize = () => {
clearTimeout(resizeTimeout);
let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
const debouncedResize = (): void => {
if (resizeTimeout !== null) clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(updateCanvasSize, 300);
};
window.addEventListener("resize", debouncedResize);
const drawMediaToCanvas = () => {
if (!ctx) return;
return () => {
window.removeEventListener("resize", debouncedResize);
if (resizeTimeout !== null) clearTimeout(resizeTimeout);
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = 0;
canvas.height = 0;
};
}, [isVideo, backgroundCanvasRef]);
// 仅在视频实际播放时进行低频采样绘制,暂停时不占 CPU/GPU
useEffect(() => {
if (!isVideo || !isPlaying) return;
const canvas = backgroundCanvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const drawMediaToCanvas = (): void => {
if (document.hidden) return;
const sourceElement = videoRef.current;
if (
!sourceElement ||
(sourceElement instanceof HTMLVideoElement &&
sourceElement.readyState < 2)
)
sourceElement.readyState < 2 ||
sourceElement.paused
) {
return;
}
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const sourceWidth = sourceElement.videoWidth;
const sourceHeight = sourceElement.videoHeight;
if (!sourceWidth || !sourceHeight) return;
if (!canvasWidth || !canvasHeight || !sourceWidth || !sourceHeight) {
return;
}
const canvasRatio = canvasWidth / canvasHeight;
const sourceRatio = sourceWidth / sourceHeight;
let drawWidth: number,
drawHeight: number,
offsetX: number,
offsetY: number;
let drawWidth: number;
let drawHeight: number;
let offsetX: number;
let offsetY: number;
if (canvasRatio > sourceRatio) {
drawWidth = canvasWidth;
@@ -85,12 +101,14 @@ export function useBackgroundCanvas({
ctx.drawImage(sourceElement, offsetX, offsetY, drawWidth, drawHeight);
};
const intervalId = setInterval(drawMediaToCanvas, 34);
drawMediaToCanvas();
const intervalId = window.setInterval(
drawMediaToCanvas,
CANVAS_DRAW_INTERVAL_MS,
);
return () => {
clearInterval(intervalId);
window.removeEventListener("resize", debouncedResize);
clearTimeout(resizeTimeout);
window.clearInterval(intervalId);
};
}, [isVideo, idx, videoRef, scrollerRef, backgroundCanvasRef]);
}, [isVideo, isPlaying, videoRef, backgroundCanvasRef]);
}