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
videotag, 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
transformand 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
videotag.
2. Restoring native video controls
Rotating the
videotag 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
topand
left.
<code>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>
)}</>
);
};
</code>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
100vhand
100vwresolves the issue.
<code>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);
}
};
</code>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.
<code>// 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);
};
</code>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.
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.