Add Rotation and Scaling to Video Previews with React and Vime
This article explains how to implement video rotation, fullscreen handling, and proportional scaling in a React application using the Vime library and CSS transforms, covering container setup, control customization, and code examples for a seamless user experience.
Background
Initially we previewed videos with a simple video tag, but the product requested rotation and proportional scaling because the videos were recorded on phones with varying orientations and small dimensions.
Implementation
The solution uses CSS transform and a wrapper container to rotate the video without affecting the control bar, leveraging the third‑party Vime component for UI.
1. Original effect
The initial preview behaved like an image preview: clicking opened a modal that played the video using only the video tag.
2. Restoring native video controls
Rotating the video tag directly would also rotate its control bar, which is undesirable. Therefore we wrap the video in a container and place a custom control bar alongside it.
To avoid building a custom UI, we use the Vime component ( vime ) which provides built‑in controls.
3. Rotation
After setting up the wrapper, we apply transform: rotate(...) to the video and swap width/height values. Because Vime sets the video to position: absolute, we also adjust top and 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);
const [aspectRatio, setAspectRatio] = useState('9:16');
const [width, setWidth] = useState<string | number>(INIT_WIDTH);
const degRef = useRef(0);
useEffect(() => {
setVisible(!!src);
}, [src]);
const closeVideoPreview = () => setVisible(false);
const onRotate = () => {
const videoEl = document.querySelector('.sc-vm-file');
if (!videoEl) return;
const deg = degRef.current < 270 ? degRef.current + 90 : 0;
let newRatio = '16:9';
let newWidth = INIT_HEIGHT;
let resWidth = `${INIT_WIDTH}px`;
let resHeight = `${INIT_HEIGHT}px`;
let top = (INIT_WIDTH - INIT_HEIGHT) / 2;
let left = (INIT_HEIGHT - INIT_WIDTH) / 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={e => e.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>
)}</>
);
};The rotation works as shown:
4. Fullscreen
When entering fullscreen, the previously fixed width/height cause the rotated video to appear incorrectly. Setting fullscreen dimensions to 100vh and 100vw resolves the issue.
const player = useRef<HTMLVmPlayerElement>(null);
const changeStyle = (deg) => {
const videoEl = document.querySelector('.sc-vm-file');
if (!videoEl) return;
const screenWidth = window.innerWidth;
const screenFullHeight = screen.height;
// ...
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);
}
};Fullscreen + rotation demo:
5. Proportional Scaling
Scaling also affects rotation, so we keep track of the last transformation using a ref and apply both scale and rotation together.
// Scale state
const [scale, setScale] = useState('1');
const changeScale = (event) => {
const radio = event.target;
const scaleVal = Number(radio.value);
setScale(radio.value);
setOpenMenu(false);
const videoEl = 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);
};
const scaleRef = useRef(1);
const changeStyle = (deg) => {
const videoEl = document.querySelector('.sc-vm-file');
if (!videoEl) return;
const vWidth = INIT_WIDTH * scaleRef.current;
const vHeight = INIT_HEIGHT * scaleRef.current;
// Apply rotation and scaling logic here
};
const changeScale = (event) => {
const radio = event.target;
scaleRef.current = Number(radio.value);
setScale(radio.value);
changeStyle(degRef.current);
};Final scaling demo:
Conclusion
Implementing video rotation and scaling is straightforward once you manipulate CSS properties correctly. When faced with seemingly complex UI requirements, a thoughtful approach and hands‑on experimentation often reveal simple solutions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Goodme Frontend Team
Regularly sharing the team's insights and expertise in the frontend field
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
