Frontend Development 10 min read

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.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
Add Rotation and Scaling to Video Previews with React and Vime

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

.

<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&lt;string | number&gt;(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

100vh

and

100vw

resolves the issue.

<code>const player = useRef&lt;HTMLVmPlayerElement&gt;(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.

frontendreactvideorotationscalingcss-transformvime
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.