init
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
|
||||
type HoverVideoProps = {
|
||||
videoUrl: string;
|
||||
coverUrl?: string | null;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
/**
|
||||
* 鼠标移入才加载视频并自动播放,移出暂停并释放资源;默认显示封面。
|
||||
*/
|
||||
export default function HoverVideo({ videoUrl, coverUrl, className, style }: HoverVideoProps) {
|
||||
const [active, setActive] = useState(false);
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
|
||||
const onEnter = useCallback(() => {
|
||||
setActive(true);
|
||||
// 播放在下一帧触发,避免 ref 尚未赋值
|
||||
requestAnimationFrame(() => {
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
v.play().catch(() => {});
|
||||
});
|
||||
}, [videoUrl]);
|
||||
|
||||
const onLeave = useCallback(() => {
|
||||
const v = videoRef.current;
|
||||
if (v) {
|
||||
try {
|
||||
v.pause();
|
||||
v.load();
|
||||
} catch {}
|
||||
}
|
||||
setActive(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={className} style={style} onMouseEnter={onEnter} onMouseLeave={onLeave}>
|
||||
{/* 封面始终渲染在底层 */}
|
||||
<img
|
||||
src={coverUrl || '/placeholder.svg'}
|
||||
alt="cover"
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
draggable={false}
|
||||
loading='lazy'
|
||||
/>
|
||||
|
||||
{/* 仅在激活后渲染视频;初始不设置 src,防止提前加载 */}
|
||||
{active ? (
|
||||
<video
|
||||
ref={videoRef}
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
autoPlay
|
||||
src={videoUrl}
|
||||
preload="none"
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user