0

I'm trying to display videos on a page, and I want to use border-radius to round the corners.

The videos themselves can be of varying aspect ratios, and I'm trying to fit them in containers while preserving their aspect ratios. Both the dimensions of the containers and videos may change over time. The videos in particular may shrink or increase in size due to the nature of their sources (getUserMedia/streaming).

Sometimes they become too small, meaning that simply setting max-width and max-height will not work as I want the videos to fill as much space as they can inside the container.

From what I understand and tested, object-fit: contain is the best method for fitting videos in containers while preserving aspect ratios but cannot be directly used with border-radius when the video fails to fill the whole element.

Based off of this answer, it appears that this isn't possible through pure CSS? Just to be sure, I'd like to know if this is still the case.

To summarize:

  • max-width and max-height alone did not work because sometimes the video is too small and I need it to expand as much as possible within the container
  • object-fit: contain did not work because it did not work with border-radius in various circumstances

Below is code where I'm testing out using JS to determine and compare aspect ratios of parent (border) and video so that I can decide whether to use width: 100% or height: 100%. The CSS are all defined based off of classnames in a separate file.

const DisplayVideo = ({
  videoSrc = null,
  // other props
}) => {
  const videoRef = useRef(null);
  const borderRef = useRef(null);
  const [fillMode, setFillMode] = useState(''); // fill-width or fill-height

  useEffect(() => {
    if (videoRef && videoRef.current && videoSrc) {
      if (videoRef.current.srcObject !== videoSrc) {
        videoRef.current.srcObject = videoSrc;
      }

      videoRef.current.play();
    }
  }, [videoRef, videoSrc]);

  useEffect(() => {
    // setting fillMode code here
    const video = videoRef.current;
    const border = borderRef.current;

    if (video && border) {
      const vAspectRatio = video.videoWidth / video.videoHeight;
      const bAspectRatio = border.clientWidth / border.clientHeight;
      setFillMode((vAspectRatio > bAspectRatio) ? 'fill-width' : 'fill-height');
    }
  });

  return (
    <div className="display-video-wrapper" ref={borderRef}>
      <video className={`display-video ${fillMode}`} ref={videoRef} />
      // other stuff
    </div>
  );
};

const mapStateToProps = (state, ownProps) => {
  // stuff
  return { videoSrc: remoteVideoSrcs[mid] };
};

export default connect(mapStateToProps)(DisplayVideo);

The issue is that the videoWidth and videoHeight values in the useEffect are all 0 when it runs. When I go to the page's console and call document.getElementsByClassName(...) to access the same values, they are the correct values, not 0. Perhaps useEffect isn't the best place to put this code? Due to redux, it's only running after a different videoSrc is set. I want it to run whenever the values of videoWidth and videoHeight changes.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181

1 Answers1

0

For simplicity's sake, I will refer to the function to compare the video and the parent's aspect ratio and determine to constrain the video by height or width as the "onResize."

The solution is to call onResize when:

  1. the video source changes dimensions
  2. the parent changes dimensions

Simply calling onResize during useEffect (occurs after every render) is insufficient.

The video element has an onresize event where we can call the comparison function. This also does not rely waiting for re-renders like useEffect, which is important because the video source could change its dimensions at any moment.

videoElement.onresize = onResize;

The parent container can change its dimensions on window resize and/or other state changes in the app. We can apply listeners to the main window's onresize event in useEffect when the component mounts.

 useEffect(() => {
   if (typeof window !== 'undefined') {
     window.addEventListener('resize', onResize);
   }

   return () => {
     if (typeof window !== 'undefined') {
       window.removeEventListener('resize', onResize);
     }
   };
 }, []);

Lastly, we still need onResize in a useEffect with dependencies on any state that affects the size of the parent container. For my case, the total number of videos to render on screen affects the parent container dimensions. So I have something like this:

useEffect(() => { onResize() }, [numberOfVideos]);