Maison  >  Article  >  interface Web  >  Création d'un système de grille dynamique pour les vignettes de réunion réactives dans React

Création d'un système de grille dynamique pour les vignettes de réunion réactives dans React

WBOY
WBOYoriginal
2024-08-28 06:03:06260parcourir

Building a Dynamic Grid System for Responsive Meeting Tiles in React

À l'ère du travail à distance et des réunions virtuelles, la création d'un système de grille réactif et dynamique pour afficher les vignettes vidéo des participants est cruciale. Inspiré par des plateformes comme Google Meet, j'ai récemment développé un système de grille flexible dans React qui s'adapte parfaitement à différents nombres de participants et à différentes tailles d'écran. Dans cet article de blog, je vais vous guider à travers la mise en œuvre, en expliquant les composants clés et comment ils fonctionnent ensemble pour créer une mise en page efficace et réactive.

Table des matières

  1. Présentation
  2. Définitions de disposition de grille
  3. Sélection de la disposition de grille appropriée
  4. Le crochet useGridLayout
  5. Exemple d'utilisation
  6. Style de la grille
  7. Conclusion

Introduction

Créer un système de grille dynamique implique d'ajuster la disposition en fonction du nombre d'éléments (ou "tuiles") et de l'espace d'écran disponible. Pour les applications de visioconférence, cela garantit que le flux vidéo de chaque participant s'affiche de manière optimale, quel que soit le nombre de participants ou l'appareil utilisé.

La solution que j'ai développée exploite les hooks React et CSS Grid pour gérer et restituer la disposition de la grille de manière dynamique. Plongeons dans les composants principaux de ce système.

Définitions de disposition de grille

Tout d'abord, nous définissons les dispositions de grille possibles que notre système peut utiliser. Chaque disposition précise le nombre de colonnes et de lignes, ainsi que les contraintes sur le nombre minimum et maximum de tuiles qu'elle peut accueillir.

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 },
];

Explication

  • GridLayoutDefinition : Un type TypeScript qui définit les propriétés de chaque disposition de grille.
  • GRID_LAYOUTS : un tableau de mises en page prédéfinies, classées par complexité. Chaque mise en page précise :
    • colonnes et lignes : le nombre de colonnes et de lignes dans la grille.
    • name : un nom descriptif pour la mise en page (par exemple, "2x2").
    • minTiles et maxTiles : la plage de nombres de tuiles que la mise en page peut accueillir.
    • minWidth et minHeight : les dimensions minimales du conteneur requises pour la mise en page.

Sélection de la disposition de grille appropriée

La logique de base permettant de sélectionner la bonne disposition de grille en fonction du nombre de tuiles et de la taille du conteneur est encapsulée dans la fonction selectGridLayout.

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];
}

Comment ça marche

  1. Sélection initiale : la fonction parcourt le tableau des mises en page pour trouver la première mise en page où maxTiles est supérieur ou égal à TileCount et garantit qu'il n'y a pas de mise en page plus grande avec les mêmes maxTiles disponibles.

  2. Mécanisme de repli : si aucune mise en page appropriée n'est trouvée, la mise en page la plus grande disponible est définie par défaut et enregistre un avertissement.

  3. Ajustement réactif : Si les contraintes minWidth ou minHeight de la mise en page sélectionnée ne sont pas respectées par les dimensions du conteneur, la fonction sélectionne de manière récursive une mise en page plus petite qui correspond aux contraintes.

  4. Retour final : La disposition sélectionnée est renvoyée, garantissant que la grille est à la fois adéquate pour le nombre de tuiles et s'adapte à la taille du conteneur.

Le crochet useGridLayout

Pour encapsuler la logique de sélection de grille et la rendre réutilisable entre les composants, j'ai créé le hook personnalisé useGridLayout.

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 };
}

Panne du crochet

  • Paramètres :

    • gridRef : Une référence à l’élément conteneur de grille.
    • TileCount : Le nombre actuel de tuiles à afficher.
  • Gestion de l'état : utilise useState pour garder une trace de la mise en page actuelle, en initialisant avec la première mise en page dans GRID_LAYOUTS.

  • Effet Crochet :

    • Fonction updateLayout : récupère la largeur et la hauteur du conteneur, sélectionne la mise en page appropriée à l'aide de selectGridLayout et met à jour l'état. Il définit également les variables CSS --col-count et --row-count sur le conteneur pour le style.
    • Event Listener : ajoute un écouteur d'événement de redimensionnement pour mettre à jour la mise en page chaque fois que la taille de la fenêtre change. Nettoie l'écouteur lors du démontage du composant.
  • Valeur de retour : Fournit l'objet de mise en page actuel au composant consommateur.

Exemple d'utilisation

Pour démontrer le fonctionnement pratique de ce système de grille dynamique, voici un exemple de composant React qui utilise le hook useGridLayout.

'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>
  )
}

Répartition des composants

  1. Gestion de l'État :

    • 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;
}

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)`,
}}

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!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn