À 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.
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.
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 }, ];
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]; }
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.
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.
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.
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.
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 }; }
Paramètres :
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 :
Valeur de retour : Fournit l'objet de mise en page actuel au composant consommateur.
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> ) }
Gestion de l'État :
Refs:
Using the Hook:
Event Handling:
Rendering:
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; }
In the useGridLayout hook, the following CSS variables are set based on the selected layout:
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.
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!