概述
VideoPlay 是一个基于 React 的高效视频播放器组件,提供了简洁的 API 和完整的播放控制功能,支持视频方向检测和响应式设计。
组件特性
-
🔄 支持 forwardRef 引用传递
-
🎮 完整的播放控制(播放/暂停)
-
📱 自动检测视频方向(横屏/竖屏)
-
🎯 响应式设计适配
-
🖼️ 自定义封面图支持
-
🔊 静音和内联播放优化
安装与引入
import VideoPlay from '../../utils/videoPlay/VideoPlay';
基础用法
1. 基本视频播放
import { useState, useRef } from 'react';
const App = () => {
const [isPlaying, setIsPlaying] = useState(false);
const videoRef = useRef(null);
return (
<VideoPlay
ref={videoRef}
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
videoSrc="path/to/video.mp4"
poster="path/to/poster.jpg"
/>
);
};
2. 完整示例(关注公众号场景)
DOM实践
import styles from './FollowPublicCode.module.scss';
import { Button } from 'antd';
import VideoPlay from '../../utils/videoPlay/VideoPlay';
import { useState, useRef } from 'react';
const FollowPublicCode = () => {
// 关注公众号视频播放状态
const [isPlaying, setIsPlaying] = useState(false);
// 关注公众号视频是否已开始播放
const [hasStarted, setHasStarted] = useState(false);
// 关注公众号视频组件引用
const videoComponentRef = useRef(null);
// 关注公众号视频元素引用
const codeVideoRef = useRef(null);
// 处理播放按钮点击事件
const handlePlayClick = () => {
console.log('点击播放按钮');
setIsPlaying(true);
setHasStarted(true);
if (codeVideoRef.current) {
codeVideoRef.current.classList.add(styles.playing);
}
};
// 处理视频播放状态变化事件
const handleVideoStateChange = (playing) => {
setIsPlaying(playing);
if (codeVideoRef.current) {
if (playing) {
codeVideoRef.current.classList.add(styles.playing);
} else {
codeVideoRef.current.classList.remove(styles.playing);
}
}
};
return (
<>
<title>
关注公众号
</title>
<div className={styles.followPublicCodeContainer}>
<div className={styles.header}>
<div className={styles.headerText}>
<h1 className={styles.h1}>关注公众号</h1>
<p className={styles.p}>消息通知将通过公众号推送</p>
</div>
</div>
<div className={styles.codeContainer}>
<div className={styles.codeVideo}>
{!hasStarted && (
<div className={styles.videoHeader}>
<div className={styles.headerVideoText}>
<h1 className={styles.h1}>Jshinelink</h1>
<p className={styles.p}>关注公众号操作指南</p>
</div>
<div
className={styles.playButton}
onClick={handlePlayClick}
>
<img
src="https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/agg-notifs-icon/Play Circle.png"
alt="播放"
width={32}
height={32}
/>
</div>
<div className={styles.headerImage}>
<img src="https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/agg-notifs-icon/logo.png" alt="logo" />
</div>
</div>
)}
{/* 已经开始播放(无论当前是播放还是暂停)都显示视频组件 */}
{hasStarted && (
<VideoPlay
ref={videoComponentRef}
isPlaying={isPlaying}
setIsPlaying={handleVideoStateChange}
videoSrc="https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/agg-notifs-icon/test_video_1.mp4"
/>
)}
</div>
<div className={styles.codeContent}>
<div className={styles.codeImgContent}>
<img className={styles.codeImg} src="https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/ceshi.png" alt="关注公号" />
</div>
<div className={styles.codeImgText}>
<p className={styles.p}>请使用微信扫一扫或长按识别二维码</p>
<Button className={styles.viewButton}>长按识别二维码</Button>
</div>
</div>
</div>
</div>
</>
);
};
export default FollowPublicCode;
CSS实践
.codeContainer {
width: 100%;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
background-color: #fff;
padding: 10px;
flex: 1;
margin: 0 auto;
.codeVideo {
width: 100%;
height: 100%;
max-height: 260px;
margin-bottom: 40px;
border-radius: 4px;
background-image: url('https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/agg-notifs-icon/Mask group.png');
background-size: cover;
position: relative;
overflow: hidden;
.videoPlayContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
&.playing {
background-image: none;
background-color: #000;
}
.videoHeader {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
min-width: 0;
padding: 20px;
box-sizing: border-box;
position: relative;
.headerVideoText {
flex: 1;
min-width: 0;
padding-left: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.h1 {
font-size: clamp(16px, 4vw, 20px);
font-weight: 500;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.p {
font-size: clamp(12px, 3vw, 16px);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.playButton {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 2;
img {
width: clamp(24px, 8vw, 32px);
height: clamp(24px, 8vw, 32px);
}
}
.headerImage {
margin-left: 10px;
flex-shrink: 0;
img {
width: clamp(80px, 30vw, 149px);
height: clamp(80px, 30vw, 149px);
}
}
}
}
}
:fullscreen {
.codeVideo {
height: 100vh !important;
max-width: 100vw !important;
border-radius: 0 !important;
background-image: none !important;
}
}
:-webkit-full-screen {
.codeVideo {
height: 100vh !important;
max-width: 100vw !important;
border-radius: 0 !important;
background-image: none !important;
}
}
// 响应式调整
@media (max-width: 480px) {
.codeContainer {
.codeVideo {
.videoHeader {
padding: 15px;
.headerVideoText {
padding-left: 5px;
.h1 {
font-size: 20px;
margin-bottom: 5px;
}
.p {
font-size: 16px;
}
}
.headerImage {
margin-left: 5px;
img {
width: 149px;
height: 149px;
}
}
}
}
.codeContent {
.codeImgContent,
.codeImgText {
padding: 0 20px;
}
.viewButton {
width: 200px;
height: 44px;
font-size: 14px;
}
}
}
}
@media (max-width: 320px) {
.codeContainer {
.codeVideo {
.videoHeader {
padding: 10px;
.headerVideoText {
.h1 {
font-size: 12px;
}
.p {
font-size: 10px;
}
}
.headerImage img {
width: 60px;
height: 60px;
}
.playButton img {
width: 20px;
height: 20px;
}
}
}
}
}
API 文档
Props
| 属性 | 类型 | 必需 | 默认值 | 描述 |
|---|---|---|---|---|
isPlaying |
boolean | 是 | - | 控制视频播放状态 |
setIsPlaying |
function | 是 | - | 播放状态变更回调 |
videoSrc |
string | 是 | - | 视频源文件路径 |
poster |
string | 否 | - | 视频封面图路径 |
Ref 方法
通过 ref 可以访问以下方法:
const videoRef = useRef(null);
// 播放视频
videoRef.current.play();
// 暂停视频
videoRef.current.pause();
// 获取原生 video 元素
const videoElement = videoRef.current.getVideoElement();
事件处理
内置事件
组件内部处理以下事件:
-
onLoadedMetadata: 检测视频元数据,自动判断方向 -
onPlay: 播放时更新状态 -
onPause: 暂停时更新状态 -
onEnded: 播放结束时重置状态
视频方向检测
组件自动检测视频方向并添加相应的 CSS 类:
-
竖屏视频:添加
portrait类 -
横屏视频:添加
landscape类
CSS 样式示例
// 竖屏视频样式
&.portrait {
.nativeVideo {
object-fit: contain;
height: 100%;
width: auto;
max-width: 100%;
}
}
// 横屏视频样式
&.landscape {
.nativeVideo {
object-fit: contain;
width: 100%;
height: auto;
max-height: 100%;
}
}
最佳实践
1. DOM管理
import React, { forwardRef, useImperativeHandle, useRef, useEffect, useState } from "react";
import styles from './video.module.scss';
const VideoPlay = forwardRef((props, ref) => {
const { isPlaying, setIsPlaying, poster, videoSrc } = props;
const videoRef = useRef(null);
const [videoOrientation, setVideoOrientation] = useState('landscape');
useImperativeHandle(ref, () => ({
play: () => {
if (videoRef.current) {
videoRef.current.play().catch(err => {
console.error('播放失败:', err);
});
}
},
pause: () => {
if (videoRef.current) {
videoRef.current.pause();
}
},
getVideoElement: () => videoRef.current
}));
useEffect(() => {
if (videoRef.current) {
if (isPlaying) {
videoRef.current.play().catch(err => {
console.error('播放失败:', err);
});
} else {
videoRef.current.pause();
}
}
}, [isPlaying]);
// 检测视频元数据,判断方向
const handleLoadedMetadata = () => {
if (videoRef.current) {
const { videoWidth, videoHeight } = videoRef.current;
const orientation = videoHeight > videoWidth ? 'portrait' : 'landscape';
setVideoOrientation(orientation);
console.log(`视频方向: ${orientation}, 尺寸: ${videoWidth}x${videoHeight}`);
}
};
return (
<div className={`${styles.videoPlayContainer} ${styles[videoOrientation]}`}>
<video
ref={videoRef}
className={styles.nativeVideo}
poster={poster}
muted
playsInline
controls
onLoadedMetadata={handleLoadedMetadata}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onEnded={() => setIsPlaying(false)}
>
<source src={videoSrc} type="video/mp4" />
您的浏览器不支持视频播放
</video>
</div>
);
});
export default VideoPlay;
2.CSS管理
.videoPlayContainer {
width: 100%;
height: 100%;
position: relative;
background: #000;
display: flex;
align-items: center;
justify-content: center;
// 竖屏视频样式
&.portrait {
.nativeVideo {
object-fit: contain;
height: 100%;
width: auto;
max-width: 100%;
}
}
// 横屏视频样式
&.landscape {
.nativeVideo {
object-fit: contain;
width: 100%;
height: auto;
max-height: 100%;
}
}
.nativeVideo {
background: #000;
display: block;
// 默认样式
max-width: 100%;
max-height: 100%;
}
}
最终呈现
点击播放按钮播放视频

视频播放(横向)

视频播放(纵向)
播放器支持放大缩小、是否静音、是否暂停等基础组件功能!
注意事项
-
移动端适配 : 确保设置
playsInline和muted属性以支持自动播放 -
视频格式: 提供多种视频格式或使用兼容性好的 MP4 格式
-
错误边界: 添加适当的错误处理机制
-
加载状态: 考虑添加视频加载中的提示
故障排除
常见问题
-
视频无法播放
-
检查视频路径是否正确
-
验证视频格式兼容性
-
确认服务器 CORS 设置
-
-
自动播放被阻止
-
确保视频设置为静音
-
添加用户交互触发播放
-
-
方向检测不准确
-
确认视频元数据已加载完成
-
检查视频尺寸信息
-
结语
VideoPlay 组件提供了简单易用的视频播放解决方案,通过合理的封装和完整的 API 设计,可以快速集成到各种 React 应用中。根据具体需求,您可以进一步扩展其功能或自定义样式。
注意⚠:该组件仅供参考!!!