React如何實現視頻旋轉縮放
一、背景
原本我們預覽視頻僅僅是簡單的video標簽實現就可以滿足業(yè)務的需求了,但是某一天,產品說:”業(yè)務的視頻是用手機拍的,方向不一定是正的,還有視頻的寬高太小了看不清,所以希望我們能讓視頻做到旋轉跳躍(不是)旋轉+按比例縮小放大“。不過吐槽歸吐槽,既然需求合理該做還是得做啊。
二、實現
這個功能初時看起來很頭疼,在細細思考下來后發(fā)現,實現思路其實并不復雜,可以通過transform去輾轉騰挪,最終完成我們想要的效果。
1. 原始效果

我們最初始的效果就是如同圖片預覽一般,點擊彈窗播放視頻,所以這里僅僅使用了video標簽。
2. 還原原生video控制欄功能
如果我們直接去旋轉video標簽,那么他的控制欄也會跟著旋轉,這并不符合我們的期望,所以我們需要在video標簽外加一層容器,然后自定義控制欄和viode同級,做到只旋轉video標簽。

但是這里我嫌自定義控制欄太麻煩,于是這里選擇了一個自帶UI的三方視頻組件進行改造。這里采用的是vime,有興趣了解的可以去vimejs.com/。最終是展示這個樣子

3. 旋轉
以上前置工作做完之后,接下來就進入正題,添加我們的旋轉功能。利用transform對video進行旋轉,同時寬高數值對換。由于vime中video標簽設置了position: absolute,所以這里我們還要調整初始的top跟left
import { Player, Video, DefaultUi, Settings, MenuItem } from '@vime/react';
// 初始寬度
const INIT_WIDTH = 370;
// 初始高度
const INIT_HEIGHT = 658;
const Demo = ({ src }) => {
const [visible, setVisible] = useState(false);
// vime組件寬高比例
const [aspectRatio, setAspectRatio] = useState('9:16');
// 容器寬度,vime組件會根據容器寬高自適應
const [width, setWidth] = useState<string | number>(INIT_WIDTH);
// 旋轉角度
const degRef = useRef(0);
useEffect(() => {
if (src) {
setVisible(true);
} else {
setVisible(false);
}
}, [src]);
const closeVideoPreview = () => {
setVisible(false);
};
const onRotate = () => {
const videoEl: HTMLVideoElement | null = document.querySelector('.sc-vm-file');
if (!videoEl) return;
const vWidth = INIT_WIDTH;
const vHeight = INIT_HEIGHT;
const deg = degRef.current < 270 ? degRef.current + 90 : 0;
// 旋轉后樣式
let newRatio = '16:9';
let newWidth = vHeight;
let resWidth = `${vWidth}px`;
let resHeight = `${vHeight}px`;
let top = (vWidth - vHeight) / 2;
let left = (vHeight - vWidth) / 2;
// 水平角度復原處理
if (deg === 0 || deg === 180) {
newWidth = INIT_WIDTH;
newRatio = '9:16';
resWidth = '100%';
resHeight = '100%';
top = 0;
left = 0;
}
videoEl.style.width = resWidth;
videoEl.style.height = resHeight;
videoEl.style.transform = `rotate(${deg}deg)`;
videoEl.style.top = `${top}px`;
videoEl.style.left = `${left}px`;
setWidth(newWidth);
setAspectRatio(newRatio);
};
return (
<>
{visible ? (
<div className='video-preview'>
<div className='video-preview-mask' onClick={() => closeVideoPreview()} />
<div className="video-preview-content" onClick={() => closeVideoPreview()}>
<div
className="video-preview-box"
style={{ width }}
onClick={(event) => {
event.stopPropagation();
}}
>
<Player
icons="custom"
aspectRatio={aspectRatio}
>
<Video>
<source data-src={src} />
</Video>
<DefaultUi noControls>
// 。。。
<Settings active={openMenu}>
<MenuItem label="旋轉" onClick={onRotate} />
</Settings>
</DefaultUi>
</Player>
</div>
</div>
</div>
) : null}
</>
);
};
此時就能實現以下效果:

4. 全屏
上面雖然已經實現了旋轉效果,但是并沒有結束,當我們點擊全屏功能之后,此時旋轉的樣式,就變得奇怪了

這是由于我們先前寫死了寬高數值,導致旋轉后video寬高沒有適應全屏,將全屏的寬高設置為100vh跟100vw即可兼容全屏狀態(tài)下的旋轉。
const player = useRef<HTMLVmPlayerElement>(null);
const changeStyle = (deg: number) => {
const videoEl: HTMLVideoElement | null = document.querySelector('.sc-vm-file');
if (!videoEl) return;
// 獲取窗口的寬度
const screenWidth = window.innerWidth;
// 獲取整個屏幕的高度
const screenFullHeight = screen.height;
const vWidth = INIT_WIDTH;
const vHeight = INIT_HEIGHT;
// 旋轉后樣式
// ...
// 當全屏旋轉90/270度時,寬高處理
if (player.current?.isFullscreenActive) {
newWidth = 'auto';
resWidth = '100vh';
resHeight = '100vw';
top = (screenFullHeight - screenWidth) / 2;
left = (screenWidth - screenFullHeight) / 2;
}
// ...
};
// 旋轉
const onRotate = () => {
setOpenMenu(false);
const newDeg = degRef.current < 270 ? degRef.current + 90 : 0;
degRef.current = newDeg;
changeStyle(newDeg);
};
// 全屏
const onVmFullscreenChange = () => {
if (degRef.current === 90 || degRef.current === 270) {
changeStyle(degRef.current);
}
};
return (
...
<Player
ref={player}
aspectRatio={aspectRatio}
onVmFullscreenChange={onVmFullscreenChange}
>
...
</Player>
)
全屏+旋轉 效果展示

5. 比例縮放
走到這里,旋轉功能原本已經完成,但是不要忘記還有一個縮小放大功能,從全屏旋轉的例子來看,縮放功能也會影響到旋轉效果,還要對旋轉再做兼容處理。
我們先實現縮放功能:
// 比例
const [scale, setScale] = useState('1');
// 比例縮放
const changeScale = (event: Event) => {
const radio = event.target as HTMLVmMenuRadioElement;
const scaleVal = Number(radio.value);
setScale(radio.value);
setOpenMenu(false);
const videoEl: HTMLVideoElement | null = document.querySelector('.sc-vm-file');
if (!videoEl) return;
// 寬高 * 選中比例
const vWidth = INIT_WIDTH * scaleVal;
const vHeight = INIT_HEIGHT * scaleVal;
// 視頻設置新寬高
videoEl.style.width = `${vWidth}px`;
videoEl.style.height = `${vHeight}px`;
setWidth(vWidth);
};
return (
...
<Submenu label="縮放比例" hint={scale}>
<MenuRadioGroup value={scale} onVmCheck={changeScale}>
<MenuRadio label="1" value="1" />
<MenuRadio label="1.2" value="1.2" />
<MenuRadio label="1.4" value="1.4" />
<MenuRadio label="1.6" value="1.6" />
<MenuRadio label="1.8" value="1.8" />
<MenuRadio label="2" value="2" />
</MenuRadioGroup>
</Submenu>
...
)
我們在縮放時改變了寬高,但是旋轉使用的依然是初始寬高,那么在縮放之后旋轉,會變回初始大小,而旋轉后再縮放,則會導致樣式錯誤。所以,我們在縮放和旋轉方法中需要拿到上一次變換過的樣式,再進行新的變換。
const scaleRef = useRef(1);
const changeStyle = (deg: number) => {
const videoEl: HTMLVideoElement | null = document.querySelector('.sc-vm-file');
if (!videoEl) return;
// 獲取窗口的寬度
const screenWidth = window.innerWidth;
// 獲取整個屏幕的高度
const screenFullHeight = screen.height;
const vWidth = INIT_WIDTH * scaleRef.current;
const vHeight = INIT_HEIGHT * scaleRef.current;
// 旋轉后樣式
// ...
// 水平角度處理
if (deg === 0 || deg === 180) {
newWidth = INIT_WIDTH * scaleRef.current;
// ...
}
// ...
};
// 比例縮放
const changeScale = (event: Event) => {
const radio = event.target as HTMLVmMenuRadioElement;
scaleRef.current = Number(radio.value);
setScale(radio.value);
changeStyle(degRef.current);
};
最終效果展示

三、總結
實現視頻的旋轉縮放功能實際上并不復雜,主要還是通過調整css樣式做到我們想要的效果。我們在業(yè)務中遇到這種“沒做過,看起來很麻煩”的問題時,始終應該還是抱著“只有你想不到,沒有我做不到”的態(tài)度,只要深入思考一下或者動手嘗試一下,大概就會發(fā)出“哦,原來這么簡單”的感嘆。有道是:山重水復疑無路,柳暗花明又一村。
到此這篇關于React如何實現視頻旋轉縮放的文章就介紹到這了,更多相關React視頻旋轉縮放內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
react 實現圖片正在加載中 加載完成 加載失敗三個階段的原理解析
這篇文章主要介紹了react 實現圖片正在加載中 加載完成 加載失敗三個階段的,通過使用loading的圖片來占位,具體原理解析及實現代碼跟隨小編一起通過本文學習吧2021-05-05
在?React?中使用?Context?API?實現跨組件通信的方法
在React中,ContextAPI是一個很有用的特性,可用于組件間的狀態(tài)共享,它允許跨組件傳遞數據而無需通過每個組件手動傳遞props,本文給大家介紹在?React?中如何使用?Context?API?來實現跨組件的通信,感興趣的朋友一起看看吧2024-09-09
React如何使用refresh_token實現無感刷新頁面
本文主要介紹了React如何使用refresh_token實現無感刷新頁面,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-04-04
React?中使用?RxJS?優(yōu)化數據流的處理方案
這篇文章主要為大家介紹了React?中使用?RxJS?優(yōu)化數據流的處理方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02
Reactjs?+?Nodejs?+?Mongodb?實現文件上傳功能實例詳解
今天是使用?Reactjs?+?Nodejs?+?Mongodb?實現文件上傳功能,前端我們使用?Reactjs?+?Axios?來搭建前端上傳文件應用,后端我們使用?Node.js?+?Express?+?Multer?+?Mongodb?來搭建后端上傳文件處理應用,本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧2022-06-06

