원격 근무 및 가상 회의 시대에는 참가자 비디오 타일을 표시하기 위한 반응성이 뛰어나고 동적인 그리드 시스템을 만드는 것이 중요합니다. Google Meet과 같은 플랫폼에서 영감을 받아 최근 React에서 다양한 참가자 수와 다양한 화면 크기에 원활하게 적응하는 유연한 그리드 시스템을 개발했습니다. 이 블로그 게시물에서는 구현 과정을 안내하고 주요 구성 요소와 이러한 구성 요소가 함께 작동하여 효율적이고 반응성이 뛰어난 레이아웃을 만드는 방법을 설명하겠습니다.
동적 그리드 시스템을 만들려면 항목(또는 '타일') 수와 사용 가능한 화면 공간을 기반으로 레이아웃을 조정해야 합니다. 화상 회의 애플리케이션의 경우 이는 참가자 수나 사용 중인 장치에 관계없이 각 참가자의 비디오 피드가 최적으로 표시되도록 보장합니다.
제가 개발한 솔루션은 React 후크와 CSS 그리드를 활용하여 그리드 레이아웃을 동적으로 관리하고 렌더링합니다. 이 시스템의 핵심 구성요소를 자세히 살펴보겠습니다.
먼저 시스템에서 사용할 수 있는 그리드 레이아웃을 정의합니다. 각 레이아웃은 열과 행의 수는 물론 수용할 수 있는 최소 및 최대 타일 수에 대한 제약 조건을 지정합니다.
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 }, ];
타일 수와 컨테이너 크기에 따라 올바른 그리드 레이아웃을 선택하기 위한 핵심 논리는 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]; }
초기 선택: 이 함수는 레이아웃 배열을 반복하여 maxTiles가 TileCount보다 크거나 같은 첫 번째 레이아웃을 찾고 동일한 maxTiles를 사용할 수 있는 더 큰 레이아웃이 없는지 확인합니다.
폴백 메커니즘: 적합한 레이아웃을 찾을 수 없는 경우 기본적으로 사용 가능한 가장 큰 레이아웃을 사용하고 경고를 기록합니다.
반응형 조정: 선택한 레이아웃의 minWidth 또는 minHeight 제약 조건이 컨테이너 크기에 맞지 않는 경우 함수는 제약 조건에 맞는 더 작은 레이아웃을 반복적으로 선택합니다.
최종 반환: 선택한 레이아웃이 반환되어 그리드가 타일 수에 적합하고 컨테이너 크기에 맞는지 확인합니다.
그리드 선택 로직을 캡슐화하고 여러 구성요소에서 재사용할 수 있도록 하기 위해 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 }; }
매개변수:
상태 관리: useState를 사용하여 현재 레이아웃을 추적하고 GRID_LAYOUTS의 첫 번째 레이아웃으로 초기화합니다.
효과 후크:
반환 값: 현재 레이아웃 객체를 소비 구성 요소에 제공합니다.
이 동적 그리드 시스템이 실제로 어떻게 작동하는지 보여주기 위해 다음은 useGridLayout 후크를 사용하는 React 구성 요소의 예입니다.
'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> ) }
국가 관리:
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!
위 내용은 React에서 반응형 회의 타일을 위한 동적 그리드 시스템 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!