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.

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendvideorotationscalingCSS 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

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.