Home Web Front-end JS Tutorial Building a Dynamic Grid System for Responsive Meeting Tiles in React

Building a Dynamic Grid System for Responsive Meeting Tiles in React

Aug 28, 2024 am 06:03 AM

Building a Dynamic Grid System for Responsive Meeting Tiles in React

In the era of remote work and virtual meetings, creating a responsive and dynamic grid system for displaying participant video tiles is crucial. Inspired by platforms like Google Meet, I recently developed a flexible grid system in React that adapts seamlessly to varying numbers of participants and different screen sizes. In this blog post, I'll walk you through the implementation, explaining the key components and how they work together to create an efficient and responsive layout.

Table of Contents

  1. Introduction
  2. Grid Layout Definitions
  3. Selecting the Appropriate Grid Layout
  4. The useGridLayout Hook
  5. Example Usage
  6. Styling the Grid
  7. Conclusion

Introduction

Creating a dynamic grid system involves adjusting the layout based on the number of items (or "tiles") and the available screen real estate. For video conferencing applications, this ensures that each participant's video feed is displayed optimally, regardless of the number of participants or the device being used.

The solution I developed leverages React hooks and CSS Grid to manage and render the grid layout dynamically. Let's dive into the core components of this system.

Grid Layout Definitions

First, we define the possible grid layouts that our system can use. Each layout specifies the number of columns and rows, as well as constraints on the minimum and maximum number of tiles it can accommodate.

import { useState, useEffect, RefObject } from 'react';

export type GridLayoutDefinition = {
  name: string;
  columns: number;
  rows: number;
  minTiles: number;
  maxTiles: number;
  minWidth: number;
  minHeight: number;
};

export const GRID_LAYOUTS: GridLayoutDefinition[] = [
  { columns: 1, rows: 1, name: '1x1', minTiles: 1, maxTiles: 1, minWidth: 0, minHeight: 0 },
  { columns: 1, rows: 2, name: '1x2', minTiles: 2, maxTiles: 2, minWidth: 0, minHeight: 0 },
  { columns: 2, rows: 1, name: '2x1', minTiles: 2, maxTiles: 2, minWidth: 900, minHeight: 0 },
  { columns: 2, rows: 2, name: '2x2', minTiles: 3, maxTiles: 4, minWidth: 560, minHeight: 0 },
  { columns: 3, rows: 3, name: '3x3', minTiles: 5, maxTiles: 9, minWidth: 700, minHeight: 0 },
  { columns: 4, rows: 4, name: '4x4', minTiles: 10, maxTiles: 16, minWidth: 960, minHeight: 0 },
  { columns: 5, rows: 5, name: '5x5', minTiles: 17, maxTiles: 25, minWidth: 1100, minHeight: 0 },
];
Copy after login

Explanation

  • GridLayoutDefinition: A TypeScript type that defines the properties of each grid layout.
  • GRID_LAYOUTS: An array of predefined layouts, ordered by complexity. Each layout specifies:
    • columns and rows: The number of columns and rows in the grid.
    • name: A descriptive name for the layout (e.g., '2x2').
    • minTiles and maxTiles: The range of tile counts that the layout can accommodate.
    • minWidth and minHeight: The minimum container dimensions required for the layout.

Selecting the Appropriate Grid Layout

The core logic for selecting the right grid layout based on the number of tiles and container size is encapsulated in the selectGridLayout function.

function selectGridLayout(
  layouts: GridLayoutDefinition[],
  tileCount: number,
  width: number,
  height: number,
): GridLayoutDefinition {
  let currentLayoutIndex = 0;
  let layout = layouts.find((layout_, index, allLayouts) => {
    currentLayoutIndex = index;
    const isBiggerLayoutAvailable = allLayouts.findIndex((l, i) => 
      i > index && l.maxTiles === layout_.maxTiles
    ) !== -1;
    return layout_.maxTiles >= tileCount && !isBiggerLayoutAvailable;
  });

  if (!layout) {
    layout = layouts[layouts.length - 1];
    console.warn(`No layout found for: tileCount: ${tileCount}, width/height: ${width}/${height}. Fallback to biggest available layout (${layout?.name}).`);
  }

  if (layout && (width < layout.minWidth || height < layout.minHeight)) {
    if (currentLayoutIndex > 0) {
      const smallerLayout = layouts[currentLayoutIndex - 1];
      layout = selectGridLayout(
        layouts.slice(0, currentLayoutIndex),
        smallerLayout.maxTiles,
        width,
        height,
      );
    }
  }

  return layout || layouts[0];
}
Copy after login

How It Works

  1. Initial Selection: The function iterates through the layouts array to find the first layout where maxTiles is greater than or equal to tileCount and ensures there's no larger layout with the same maxTiles available.

  2. Fallback Mechanism: If no suitable layout is found, it defaults to the largest available layout and logs a warning.

  3. Responsive Adjustment: If the selected layout's minWidth or minHeight constraints aren't met by the container dimensions, the function recursively selects a smaller layout that fits within the constraints.

  4. Final Return: The selected layout is returned, ensuring that the grid is both adequate for the number of tiles and fits within the container's size.

The useGridLayout Hook

To encapsulate the grid selection logic and make it reusable across components, I created the useGridLayout custom hook.

export function useGridLayout(
  gridRef: RefObject<HTMLElement>,
  tileCount: number
): { layout: GridLayoutDefinition } {
  const [layout, setLayout] = useState<GridLayoutDefinition>(GRID_LAYOUTS[0]);

  useEffect(() => {
    const updateLayout = () => {
      if (gridRef.current) {
        const { width, height } = gridRef.current.getBoundingClientRect();
        const newLayout = selectGridLayout(GRID_LAYOUTS, tileCount, width, height);
        setLayout(newLayout);

        gridRef.current.style.setProperty('--col-count', newLayout.columns.toString());
        gridRef.current.style.setProperty('--row-count', newLayout.rows.toString());
      }
    };

    updateLayout();

    window.addEventListener('resize', updateLayout);
    return () => window.removeEventListener('resize', updateLayout);
  }, [gridRef, tileCount]);

  return { layout };
}
Copy after login

Hook Breakdown

  • Parameters:

    • gridRef: A reference to the grid container element.
    • tileCount: The current number of tiles to display.
  • State Management: Uses useState to keep track of the current layout, initializing with the first layout in GRID_LAYOUTS.

  • Effect Hook:

    • updateLayout Function: Retrieves the container's width and height, selects the appropriate layout using selectGridLayout, and updates the state. It also sets CSS variables --col-count and --row-count on the container for styling.
    • Event Listener: Adds a resize event listener to update the layout whenever the window size changes. Cleans up the listener on component unmount.
  • Return Value: Provides the current layout object to the consuming component.

Example Usage

To demonstrate how this dynamic grid system works in practice, here's an example React component that uses the useGridLayout hook.

'use client'

import React, { useState, useRef, useEffect } from 'react'
import { Button } from "@/components/ui/button"
import { useGridLayout, GridLayoutDefinition } from './useGridLayout'

export default function Component() {
  const [tiles, setTiles] = useState<number[]>([1, 2, 3, 4]);
  const [containerWidth, setContainerWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 1000);
  const gridRef = useRef<HTMLDivElement>(null);

  const { layout } = useGridLayout(gridRef, tiles.length);

  useEffect(() => {
    const handleResize = () => {
      setContainerWidth(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  const addTile = () => setTiles(prev => [...prev, prev.length + 1]);
  const removeTile = () => setTiles(prev => prev.slice(0, -1));

  return (
    <div className="flex flex-col items-center gap-4 p-4 w-full">
      <div className="flex flex-wrap justify-center gap-2 mb-4">
        <Button onClick={addTile}>Add Tile</Button>
        <Button onClick={removeTile}>Remove Tile</Button>
      </div>
      <div className="w-full border border-gray-300 p-4">
        <div 
          ref={gridRef} 
          className="grid gap-4"
          style={{
            gridTemplateColumns: `repeat(var(--col-count), 1fr)`,
            gridTemplateRows: `repeat(var(--row-count), 1fr)`,
          }}
        >
          {tiles.slice(0, layout.maxTiles).map((tile) => (
            <div key={tile} className="bg-primary text-primary-foreground p-4 rounded flex items-center justify-center">
              Tile {tile}
            </div>
          ))}
        </div>
      </div>
      <div className="text-center mt-4">
        <p>Current Layout: {layout.name} ({layout.columns}x{layout.rows})</p>
        <p>Container Width: {containerWidth}px</p>
        <p>Visible Tiles: {Math.min(tiles.length, layout.maxTiles)} / Total Tiles: {tiles.length}</p>
      </div>
    </div>
  )
}
Copy after login

Component Breakdown

  1. State Management:

    • tiles: An array representing the current tiles. Initially contains four tiles.
    • containerWidth: Tracks the container's width, updating on window resize.
  2. Refs:

    • gridRef: A reference to the grid container, passed to the useGridLayout hook.
  3. Using the Hook:

    • Destructures the layout object from the useGridLayout hook, which determines the current grid layout based on the number of tiles and container size.
  4. Event Handling:

    • Add Tile: Adds a new tile to the grid.
    • Remove Tile: Removes the last tile from the grid.
    • Resize Listener: Updates containerWidth on window resize.
  5. Rendering:

    • Controls: Buttons to add or remove tiles.
    • Grid Container:
      • Uses CSS Grid with dynamic gridTemplateColumns and gridTemplateRows based on CSS variables set by the hook.
      • Renders tiles up to the layout.maxTiles limit.
    • Info Section: Displays the current layout, container width, and the number of visible versus total tiles.

What Happens in Action

  • Adding Tiles: As you add more tiles, the useGridLayout hook recalculates the appropriate grid layout to accommodate the new number of tiles while respecting the container's size.
  • Removing Tiles: Removing tiles triggers a layout recalculation to potentially use a smaller grid layout, optimizing space.
  • Resizing: Changing the window size dynamically adjusts the grid layout to ensure that the tiles remain appropriately sized and positioned.

Styling the Grid

The grid's responsiveness is primarily handled via CSS Grid properties and dynamically set CSS variables. Here's a brief overview of how the styling works:

/* Example Tailwind CSS classes used in the component */
/* The actual styles are managed via Tailwind, but the key dynamic properties are set inline */

.grid {
  display: grid;
  gap: 1rem; /* Adjust as needed */
}

.grid > div {
  /* Example styles for tiles */
  background-color: var(--color-primary, #3490dc);
  color: var(--color-primary-foreground, #ffffff);
  padding: 1rem;
  border-radius: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
Copy after login

Dynamic CSS Variables

In the useGridLayout hook, the following CSS variables are set based on the selected layout:

  • --col-count: Number of columns in the grid.
  • --row-count: Number of rows in the grid.

These variables are used to define the gridTemplateColumns and gridTemplateRows properties inline:

style={{
  gridTemplateColumns: `repeat(var(--col-count), 1fr)`,
  gridTemplateRows: `repeat(var(--row-count), 1fr)`,
}}
Copy after login

This approach ensures that the grid layout adapts seamlessly without the need for extensive CSS media queries.

Conclusion

Building a dynamic grid system for applications like video conferencing requires careful consideration of both the number of elements and the available display space. By defining a set of responsive grid layouts and implementing a custom React hook to manage layout selection, we can create a flexible and efficient system that adapts in real-time to user interactions and screen size changes.

This approach not only enhances the user experience by providing an optimal viewing arrangement but also simplifies the development process by encapsulating the layout logic within reusable components. Whether you're building a video conferencing tool, a dashboard, or any application that requires dynamic content arrangement, this grid system can be a valuable addition to your toolkit.

Feel free to customize and extend this system to suit your specific needs. Happy coding!

The above is the detailed content of Building a Dynamic Grid System for Responsive Meeting Tiles in React. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

Java Tutorial
1661
14
PHP Tutorial
1261
29
C# Tutorial
1234
24
Demystifying JavaScript: What It Does and Why It Matters Demystifying JavaScript: What It Does and Why It Matters Apr 09, 2025 am 12:07 AM

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

The Evolution of JavaScript: Current Trends and Future Prospects The Evolution of JavaScript: Current Trends and Future Prospects Apr 10, 2025 am 09:33 AM

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

JavaScript Engines: Comparing Implementations JavaScript Engines: Comparing Implementations Apr 13, 2025 am 12:05 AM

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript: Exploring the Versatility of a Web Language JavaScript: Exploring the Versatility of a Web Language Apr 11, 2025 am 12:01 AM

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

Python vs. JavaScript: The Learning Curve and Ease of Use Python vs. JavaScript: The Learning Curve and Ease of Use Apr 16, 2025 am 12:12 AM

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

How to Build a Multi-Tenant SaaS Application with Next.js (Frontend Integration) How to Build a Multi-Tenant SaaS Application with Next.js (Frontend Integration) Apr 11, 2025 am 08:22 AM

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

From C/C   to JavaScript: How It All Works From C/C to JavaScript: How It All Works Apr 14, 2025 am 12:05 AM

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

Building a Multi-Tenant SaaS Application with Next.js (Backend Integration) Building a Multi-Tenant SaaS Application with Next.js (Backend Integration) Apr 11, 2025 am 08:23 AM

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing

See all articles