loading...
Cover image for Building a video gallery just like in ZOOM

Building a video gallery just like in ZOOM

antondosov profile image Anton Dosov Updated on ・3 min read

Update

I posted a fun part 2, where I am building the same thing using experimental CSS Layout API from CSS Houdini 🎩. Check this out!

TLDR

Complete solution is here. Change videos count and resize the screen to see it in action.

Intro

Hi folks πŸ‘‹

I had to build a video gallery view similar to the one ZOOM has for a video-conferencing app.

I've spent quite a bit of time trying to figure out how to build it with pure CSS and I... FAILED πŸ˜•.

Sharing my current solution with

  • a bit of JavaScript
  • CSS custom properties
  • display: flex

If someone has an idea on how to achieve similar result without using JavaScript, please share πŸ™

Problem

Having videoCount videos with fixed aspectRatio and fixed container size (containerWidth, containerHeight), fit all the videos inside the container to occupy as much area as possible. Videos should have the same size and can't overflow the container.

Solution

Calculating size for a video

First I needed to make sure videos are not overflowing the container and occupying as much area as possible.

function calculateLayout(
    containerWidth: number,
    containerHeight: number,
    videoCount: number,
    aspectRatio: number
  ): { width: number; height: number, cols: number } {
 // see implementation in codesandbox
}

Current implementation brute-force searches the layout which occupies the most of available space. It compares the sum of video areas for every possible number of columns.

// pseudocode, see codesandbox for complete version
let bestArea;
for (let cols = 1; cols <= videoCount; cols++) {
   const currentArea = /* sum of video areas in this layout */
   if (bestArea < currentArea) {
      bestArea = currentArea;
   }
}

There is also an npm module that does just that!

GitHub logo fzembow / rect-scaler

Find largest rectangle and square sizes when fitting them into a container

rect-scaler

A set of javascript functions for calculating how large a set of equally sized squares or rectangles can be to fit within an arbitrary rectangular container, to cover it as fully as possible.

Illustration

Useful for graphical layouts where you need to space items in a nice way. This algorithm does not allow for rotations, and is not generic bin packing.

Usage

Install from npm:

npm install rect-scaler

Fitting squares

Pass the size of the container and the number of squares that need to be placed to largestSquare() resulting in an object containing the optimal solution.

const { largestSquare } = require('rect-scaler');
const containerWidth = 100;
const containerHeight = 100;
const numSquares = 8;
const { rows, cols, width, height, area } = largestSquare(containerWidth, containerHeight, numSquares);

Fitting rectangles

Pass the size of…

But is there a better way then brute-force? Is it worth it if we assume maximum videoCount is 50? πŸ€”

Markup, styles & CSS custom properties

The HTML structure I went with:

<body>
    <div id="gallery">
      <div class="video-container">
        <video></video>
      </div>
      <div class="video-container">
        <video></video>
      </div>
    </div>
</body

I applied calculated width and height to .video-container.

.video-container {
  width: var(--width);
  height: var(--height);
}

I used CSS custom properties to pass values calculated in JavaScript.

const gallery = document.getElementById('gallery');
gallery.style.setProperty("--width", width + "px");
gallery.style.setProperty("--height", height + "px");
gallery.style.setProperty("--cols", cols + "");

⚠️ Don't forget to recalculate these values when screen size or number of videos change.

Then I used display: flex to layout .video-container elements

#gallery {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  max-width: calc(var(--width) * var(--cols));
}

As .video-container sizes are calculated to fit into the container, I didn't have to worry about anything else here.
I also found justify-content: center; to work best for the use case, as it nicely centers not fully filled rows.
The purpose of max-width is to force line breaks.

Handling different aspect ratios

This gallery has a constraint of fixed aspect ratio for all elements. But videos could have different ratios. This is where having .video-container wrapping <video/> is handy.

.video-container {
  width: var(--width);
  height: var(--height);
}

video {
  height: 100%;
  width: 100%;
}

This way video occupies as much space as possible within its container and preserves its original aspect ratio.

For example, changing ratio of a container from 16:9 to 1:1 doesn't distort videos with original 16:9 ratio.
Different aspect ratio

Result

This is how it looks in real world:
Screenshot from Meetter with grid layout

Please find complete solution here. Change videos count and resize the screen to see it in action.

Open questions❓

  1. Is it possible to achieve similar result without calculating video size in JavaScript? πŸ€”
  2. Is there a better way of calculating video sizes then brute-force search? Is it worth it if number of videos can't exceed 50? πŸ€”

Posted on Jun 1 by:

antondosov profile

Anton Dosov

@antondosov

Reducing time wasted in meetings by building @MeetterHQ Software Engineer @elastic

Discussion

markdown guide
 

I posted a fun part 2, where I am building the same thing using experimental CSS Layout API from CSS Houdini 🎩. Check this out!