Categories: CSSScriptWeb Design

JavaScript Video Conference Grid Layout Library with Gallery & Spotlight Modes

Meet Layout Grid is a responsive, framework-agnostic grid library designed for video conference layouts with smooth motion animations.

Features:

  • Dual Layout Modes: Gallery mode with flexible grid packing and optional pin mode that splits the view into focus area plus thumbnails. Spotlight mode displays a single participant at full size.
  • Pin and Focus Support: Pin any participant to become the main view. The library repositions other participants into a thumbnail strip on the right, left, top, or bottom.
  • Spring-Based Animations: Uses Motion (Framer Motion for React, Motion One for Vue) to create smooth transitions when participants join, leave, or change position.
  • Pagination System: Split large participant groups across multiple pages. Supports separate pagination for the main grid and the thumbnail strip in pin mode.
  • Max Visible with Overflow Indicator: Limit visible participants and show a “+N more” indicator on the last visible item.
  • Flexible Aspect Ratios: Define different aspect ratios per participant. The library handles mixed ratios like 16:9 desktop and 9:16 mobile in a single grid.
  • Floating Picture-in-Picture: Draggable PiP component that snaps to corners. Includes responsive breakpoints that auto-adjust size based on container width.
  • Grid Overlay System: Full-screen overlay for screen sharing or whiteboard content that sits above the participant grid.
  • Responsive Design: The grid recalculates on container resize. Uses justified packing algorithm to minimize wasted space.
  • Framework Support: Core package is framework-agnostic. React and Vue packages provide components, hooks, and composables.
  • TypeScript Native: Ships with complete type definitions for all APIs, options, and return values.
  • Tree-Shakeable: Import only the features you need. The core package has zero dependencies.

Use Cases:

  • Video Conferencing Applications: Build Zoom-style grid layouts with gallery view, spotlight mode, and pin functionality.
  • Virtual Classroom Platforms: Display students in a grid with the teacher pinned to the main area.
  • Webinar Interfaces: Spotlight the speaker while showing audience members in a thumbnail strip.
  • Multi-Device Support: Handle participants joining from phones (9:16 portrait) and desktops (16:9 landscape) in the same grid.

See It In Action:

Basic Usage:

1. Install the package for your framework:

The React and Vue packages re-export everything from core. You do not need to install core separately when using a framework package.

# Vanilla JavaScript/TypeScript
npm install @thangdevalone/meet-layout-grid-core

# React
npm install @thangdevalone/meet-layout-grid-react

# Vue 3+
npm install @thangdevalone/meet-layout-grid-vue

2. Basic usage (Vanilla JavaScript):

import { createMeetGrid } from '@thangdevalone/meet-layout-grid-core'

// Create a grid calculator with your container dimensions
const grid = createMeetGrid({
  dimensions: { width: 800, height: 600 },  // Container size
  count: 6,                                  // Number of items
  aspectRatio: '16:9',                       // Item aspect ratio
  gap: 8,                                    // Gap between items
  layoutMode: 'gallery',                     // Layout mode
})

// Position each item using absolute positioning
for (let i = 0; i < 6; i++) {
  const { top, left } = grid.getPosition(i)
  const { width, height } = grid.getItemDimensions(i)
  
  // Apply styles to your video element
  element.style.cssText = `
    position: absolute;
    top: ${top}px;
    left: ${left}px;
    width: ${width}px;
    height: ${height}px;
  `
}

// Recalculate when container resizes
window.addEventListener('resize', () => {
  const newGrid = createMeetGrid({
    dimensions: { 
      width: container.clientWidth, 
      height: container.clientHeight 
    },
    count: 6,
    aspectRatio: '16:9',
    gap: 8,
    layoutMode: 'gallery',
  })
  
  // Update positions...
})

3. Basic usage (React):

import { GridContainer, GridItem } from '@thangdevalone/meet-layout-grid-react'
function MeetingGrid({ participants }) {
  return (
    // GridContainer manages the grid layout and animations
    <GridContainer 
      aspectRatio="16:9"      // Default aspect ratio for all items
      gap={8}                 // Gap between items in pixels
      layoutMode="gallery"    // Use flexible grid layout
      count={participants.length}  // Total number of participants
    >
      {participants.map((p, index) => (
        // GridItem handles positioning and transitions for each participant
        <GridItem key={p.id} index={index}>
          <VideoTile participant={p} />
        </GridItem>
      ))}
    </GridContainer>
  )
}

4. Basic usage (Vue):

<script setup>
import { GridContainer, GridItem } from '@thangdevalone/meet-layout-grid-vue'
import { ref } from 'vue'
const participants = ref([
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  // ... more participants
])
const pinnedIndex = ref(null)
function pinParticipant(index) {
  pinnedIndex.value = index
}
</script>
<template>
  <GridContainer 
    aspect-ratio="16:9"
    :gap="8"
    :count="participants.length"
    layout-mode="gallery"
    :pinned-index="pinnedIndex"
    others-position="right"
  >
    <GridItem 
      v-for="(p, index) in participants" 
      :key="p.id" 
      :index="index"
    >
      <VideoTile 
        :participant="p"
        @pin="pinParticipant(index)"
      />
    </GridItem>
  </GridContainer>
</template>

5. All configuration options.

  • dimensions ({ width: number, height: number }): Container size in pixels. Required for Vanilla JS API.
  • count (number): Total number of participants. Required.
  • aspectRatio (string): Default aspect ratio for all items. Accepts "16:9", "9:16", "4:3", or "auto". Default is "16:9".
  • gap (number): Gap between grid items in pixels. Default is 8.
  • layoutMode (‘gallery’ | ‘spotlight’): Layout mode. Gallery creates a flexible grid. Spotlight shows a single participant at full size. Default is 'gallery'.
  • pinnedIndex (number): Index of the pinned participant in gallery mode. When set, the layout splits into a focus area (pinned item) and thumbnails area (other participants). Default is undefined.
  • othersPosition (‘left’ | ‘right’ | ‘top’ | ‘bottom’): Position of thumbnails in pin mode. Default is 'right'.
  • maxItemsPerPage (number): Maximum items per page in gallery mode. Set to 0 to disable pagination. Default is 0.
  • currentPage (number): Current page index for gallery pagination. Zero-based. Default is 0.
  • maxVisible (number): Maximum visible items in the thumbnails area when using pin mode. Set to 0 to show all. Default is 0.
  • currentVisiblePage (number): Current page index for thumbnail pagination in pin mode. Zero-based. Default is 0.
  • itemAspectRatios ((string | undefined)[]): Array of aspect ratios for each participant. Use undefined to fall back to global aspectRatio. Length should match count.
  • floatWidth (number): Width of the auto-float PiP in 2-person mode. Overrides floatBreakpoints. Default is 120.
  • floatHeight (number): Height of the auto-float PiP in 2-person mode. Overrides floatBreakpoints. Default is 160.
  • floatBreakpoints (PipBreakpoint[]): Responsive breakpoints for auto-float PiP sizing. Each breakpoint is { minWidth: number, width: number, height: number }.
  • springPreset (‘snappy’ | ‘smooth’ | ‘gentle’ | ‘bouncy’): Animation preset for spring transitions. Default is 'smooth'.

6. API methods.

// Get the absolute position of an item in the grid
// Returns { top: number, left: number }
const position = grid.getPosition(index);

// Get the dimensions of a grid cell
// Returns { width: number, height: number }
const cellSize = grid.getItemDimensions(index);

// Get content dimensions with aspect ratio preserved
// Returns { width, height, offsetTop, offsetLeft }
// The offsets center the content within the cell
const content = grid.getItemContentDimensions(index, '16:9');

// Check if an item is visible on the current page
// Returns boolean
const visible = grid.isItemVisible(index);

// Check if an item is the main/pinned item
// Returns boolean
const isMain = grid.isMainItem(index);

// Get the index of the last visible item in the thumbnails area
// Returns number
const lastIndex = grid.getLastVisibleOthersIndex();

7. GridContainer Props (React/Vue):

// Get pagination information
// Returns PaginationInfo object:
// {
//   enabled: boolean,
//   currentPage: number,
//   totalPages: number,
//   itemsOnPage: number,
//   startIndex: number,
//   endIndex: number
// }
const pagination = grid.pagination;

// Get the number of hidden items when using maxVisible
// Returns number
const hidden = grid.hiddenCount;

8. FloatingGridItem Props:

  • children (ReactNode | VNode): Content to render inside the floating item. Required.
  • width (number): Width of the floating item in pixels. Overridden by breakpoints when provided. Default is 120.
  • height (number): Height of the floating item in pixels. Overridden by breakpoints when provided. Default is 160.
  • breakpoints (PipBreakpoint[]): Responsive breakpoints for auto-sizing. Each breakpoint is { minWidth: number, width: number, height: number }.
  • initialPosition ({ x: number, y: number }): Extra offset from the anchor corner. Default is { x: 16, y: 16 }.
  • anchor (‘top-left’ | ‘top-right’ | ‘bottom-left’ | ‘bottom-right’): Which corner to snap the item to. Default is 'bottom-right'.
  • visible (boolean): Whether the floating item is visible. Default is true.
  • edgePadding (number): Minimum padding from container edges in pixels. Default is 12.
  • onAnchorChange ((anchor: string) => void): Callback when anchor corner changes after drag.
  • transition (Transition): Custom Motion transition object for snap animation. Default is spring with stiffness 400 and damping 30.
  • borderRadius (number): Border radius of the floating item in pixels. Default is 12.
  • boxShadow (string): CSS box-shadow value. Default is '0 4px 20px rgba(0,0,0,0.3)'.
  • className (string): Additional CSS class name.
  • style (CSSProperties | object): Custom inline styles. Merged with floating styles.

9. GridOverlay Props:

  • children (ReactNode | VNode): Content to render inside the overlay. Required when visible is true.
  • visible (boolean): Whether to show the overlay. Default is true.
  • backgroundColor (string): Overlay background color. Accepts any valid CSS color. Default is 'rgba(0,0,0,0.5)'.

10. GridItem Render Props (React):

<GridItem index={0}>
  {({ isLastVisibleOther, hiddenCount }) => (
    // Your component here
  )}
</GridItem>
  • isLastVisibleOther (boolean): True if this is the last visible item in the thumbnails area when using maxVisible.
  • hiddenCount (number): Number of hidden items when using maxVisible. Use this to show “+N more” indicators.

Advanced Usage:

1. Pagination for Large Groups:

import { GridContainer, GridItem } from '@thangdevalone/meet-layout-grid-react'
import { useState } from 'react'
function MeetingGrid({ participants }) {
  const [currentPage, setCurrentPage] = useState(0)
  const maxItemsPerPage = 9  // Show 9 participants per page
  return (
    <>
      <GridContainer
        count={participants.length}
        maxItemsPerPage={maxItemsPerPage}  // Enable pagination
        currentPage={currentPage}          // Current page index (0-based)
      >
        {participants.map((p, index) => (
          <GridItem key={p.id} index={index}>
            <VideoTile participant={p} />
          </GridItem>
        ))}
      </GridContainer>
      {/* Pagination controls */}
      <div>
        <button 
          onClick={() => setCurrentPage(prev => Math.max(0, prev - 1))}
          disabled={currentPage === 0}
        >
          Previous
        </button>
        <span>Page {currentPage + 1}</span>
        <button 
          onClick={() => setCurrentPage(prev => prev + 1)}
          disabled={(currentPage + 1) * maxItemsPerPage >= participants.length}
        >
          Next
        </button>
      </div>
    </>
  )
}

For pin mode, use maxVisible and currentVisiblePage to paginate the thumbnail strip:

<GridContainer
  layoutMode="gallery"
  pinnedIndex={0}
  maxVisible={4}                    // Show 4 thumbnails at a time
  currentVisiblePage={othersPage}   // Current page for thumbnails
>

2. Limit visible participants and show overflow count:

<GridContainer maxVisible={4} count={12}>
  {participants.map((p, index) => (
    <GridItem key={p.id} index={index}>
      {/* GridItem passes isLastVisibleOther and hiddenCount as render props */}
      {({ isLastVisibleOther, hiddenCount }) => (
        <>
          {isLastVisibleOther && hiddenCount > 0 ? (
            // Show "+N more" indicator on the last visible thumbnail
            <div className="more-indicator">
              +{hiddenCount} more
            </div>
          ) : (
            // Show normal video tile
            <VideoTile participant={p} />
          )}
        </>
      )}
    </GridItem>
  ))}
</GridContainer>

3. Handle mixed aspect ratios for phone and desktop participants. Supported aspect ratio values:

  • "16:9": Standard landscape video.
  • "9:16": Portrait video for mobile devices.
  • "4:3": Classic tablet ratio.
  • "auto": Stretch to fill the cell (ignores aspect ratio).
  • undefined: Use the global aspectRatio prop.
const participants = [
  { id: 1, name: 'Alice', device: 'desktop' },
  { id: 2, name: 'Bob', device: 'phone' },
  { id: 3, name: 'Charlie', device: 'desktop' },
]
// Define aspect ratio for each participant
const itemAspectRatios = participants.map(p => {
  if (p.device === 'phone') return '9:16'    // Portrait
  if (p.device === 'desktop') return '16:9'  // Landscape
  return undefined  // Use global aspectRatio
})
<GridContainer
  aspectRatio="16:9"               // Global default
  itemAspectRatios={itemAspectRatios}  // Per-item overrides
  count={participants.length}
>
  {participants.map((p, index) => (
    <GridItem key={p.id} index={index}>
      <VideoTile participant={p} />
    </GridItem>
  ))}
</GridContainer>

4. Add a draggable PiP that snaps to corners. The anchor prop controls which corner the PiP snaps to:

  • 'top-left'
  • 'top-right'
  • 'bottom-left'
  • 'bottom-right'
import { GridContainer, GridItem, FloatingGridItem } from '@thangdevalone/meet-layout-grid-react'
import { useState } from 'react'
function MeetingGrid({ participants, floatingParticipant }) {
  const [pipAnchor, setPipAnchor] = useState('bottom-right')
  const [showPip, setShowPip] = useState(true)
  return (
    <GridContainer count={participants.length}>
      {/* Main grid items */}
      {participants.map((p, index) => (
        <GridItem key={p.id} index={index}>
          <VideoTile participant={p} />
        </GridItem>
      ))}
      {/* Floating PiP component */}
      <FloatingGridItem
        width={130}                  // PiP width in pixels
        height={175}                 // PiP height in pixels
        anchor={pipAnchor}           // Corner to snap to
        visible={showPip}            // Show/hide PiP
        edgePadding={12}             // Minimum padding from edges
        borderRadius={8}             // Corner rounding
        onAnchorChange={setPipAnchor}  // Called when user drags to new corner
      >
        <VideoTile participant={floatingParticipant} />
      </FloatingGridItem>
    </GridContainer>
  )
}

5. Use breakpoints to auto-adjust PiP size based on container width. The default breakpoints provide 5 responsive levels:

Container Width PiP Size
0 – 479px 100 × 135
480 – 767px 130 × 175
768 – 1023px 160 × 215
1024 – 1439px 180 × 240
1440px+ 220 × 295
import { 
  FloatingGridItem, 
  DEFAULT_FLOAT_BREAKPOINTS 
} from '@thangdevalone/meet-layout-grid-react'
// Use built-in breakpoints (5 levels from 100x135 to 220x295)
<FloatingGridItem breakpoints={DEFAULT_FLOAT_BREAKPOINTS}>
  <VideoTile participant={pip} />
</FloatingGridItem>
// Define custom breakpoints
const customBreakpoints = [
  { minWidth: 0, width: 80, height: 110 },       // Small screens
  { minWidth: 600, width: 150, height: 200 },    // Medium screens
  { minWidth: 1200, width: 250, height: 330 },   // Large screens
]
<FloatingGridItem breakpoints={customBreakpoints}>
  <VideoTile participant={pip} />
</FloatingGridItem>

For the auto-float PiP in 2-person mode, use floatBreakpoints on GridContainer:

<GridContainer 
  count={2} 
  floatBreakpoints={DEFAULT_FLOAT_BREAKPOINTS}
>
  {participants.map((p, i) => (
    <GridItem key={p.id} index={i}>
      <VideoTile participant={p} />
    </GridItem>
  ))}
</GridContainer>

6. Show full-screen content above the grid:

import { GridContainer, GridItem, GridOverlay } from '@thangdevalone/meet-layout-grid-react'
import { useState } from 'react'
function MeetingGrid({ participants, screenShare }) {
  const [isScreenSharing, setIsScreenSharing] = useState(false)
  return (
    <GridContainer count={participants.length}>
      {/* Grid items */}
      {participants.map((p, index) => (
        <GridItem key={p.id} index={index}>
          <VideoTile participant={p} />
        </GridItem>
      ))}
      
      {/* Full-screen overlay for screen sharing */}
      <GridOverlay 
        visible={isScreenSharing}            // Show/hide overlay
        backgroundColor="rgba(0, 0, 0, 0.9)" // Semi-transparent background
      >
        <ScreenShareView stream={screenShare} />
      </GridOverlay>
    </GridContainer>
  )
}

7. Control the animation style with presets:

Preset Use Case
snappy Fast UI feedback
smooth Layout changes (default)
gentle Subtle motion
bouncy Slight overshoot
<GridContainer springPreset="smooth">
  {/* items */}
</GridContainer>

The post JavaScript Video Conference Grid Layout Library with Gallery & Spotlight Modes appeared first on CSS Script.

rssfeeds-admin

Share
Published by
rssfeeds-admin

Recent Posts

Animal Crossing: New Horizons Update 3.0.2 Released

In honor of its Animal Crossing series' 25th anniversary, Nintendo has a special treat for…

50 minutes ago

Animal Crossing: New Horizons Update 3.0.2 Released

In honor of its Animal Crossing series' 25th anniversary, Nintendo has a special treat for…

50 minutes ago

Animal Crossing: New Horizons Update 3.0.2 Released

In honor of its Animal Crossing series' 25th anniversary, Nintendo has a special treat for…

50 minutes ago

Katy Perry Rep Responds to Ruby Rose Accusations

A representative for pop star Katy Perry has issued a strongly worded response to sexual…

51 minutes ago

Katy Perry Rep Responds to Ruby Rose Accusations

A representative for pop star Katy Perry has issued a strongly worded response to sexual…

51 minutes ago

Stan Lambert leads surprise celebration honoring Fabrizio’s retirement

ABILENE, Texas (KTAB/KRBC) – Dr. Paul Fabrizio was honored Monday at McMurry University by State…

1 hour ago

This website uses cookies.