JavaScript Video Conference Grid Layout Library with Gallery & Spotlight Modes
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.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.
In honor of its Animal Crossing series' 25th anniversary, Nintendo has a special treat for…
In honor of its Animal Crossing series' 25th anniversary, Nintendo has a special treat for…
In honor of its Animal Crossing series' 25th anniversary, Nintendo has a special treat for…
A representative for pop star Katy Perry has issued a strongly worded response to sexual…
A representative for pop star Katy Perry has issued a strongly worded response to sexual…
ABILENE, Texas (KTAB/KRBC) – Dr. Paul Fabrizio was honored Monday at McMurry University by State…
This website uses cookies.