You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于React+Emotion的固定行数响应式Grid组件开发问题

实现带自适应展开按钮的Grid组件(React + Emotion + TypeScript)

核心思路

要实现所有视口下2行布局、最后一格显示展开按钮的需求,关键在于:

  • 动态计算当前屏幕下的Grid列数(适配auto-fit的可变列特性)
  • 根据列数控制默认显示的项数,留出最后一格放置按钮
  • 用状态管理展开/折叠状态,切换Grid的行数限制

具体实现步骤

1. 状态与列数计算

用ResizeObserver监听Grid容器宽度,结合媒体查询计算当前列数(最大6列):

import { useState, useEffect, useRef } from 'react';
import styled from '@emotion/styled';
import { css } from '@emotion/react';

// 假设你的GridItem类型定义
type GridItem = {
  id: string;
  title: string;
  // 你的其他GridItem属性
};

const GridContainer = ({ gridItemsArr }: { gridItemsArr: GridItem[] }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const [columnCount, setColumnCount] = useState(6); // 默认桌面端列数
  const gridRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const calculateColumns = () => {
      if (!gridRef.current) return;
      
      // 根据媒体查询判断当前屏幕尺寸,获取GridItem宽度
      const isDesktop = window.matchMedia('(min-width: 1200px)').matches; // 替换成你的desktop断点
      const itemWidth = isDesktop ? 200 : 170;
      
      // 获取gap值(从CSS变量中读取)
      const gapValue = getComputedStyle(document.documentElement).getPropertyValue('--spacing-2xs');
      const gap = gapValue ? parseInt(gapValue) : 8;
      
      // 计算列数:容器宽度 / (项宽度 + gap),取整后不超过6列
      const containerWidth = gridRef.current.offsetWidth;
      const cols = Math.floor((containerWidth + gap) / (itemWidth + gap));
      setColumnCount(Math.min(cols, 6));
    };

    // 初始化计算
    calculateColumns();
    
    // 监听容器尺寸变化
    const observer = new ResizeObserver(calculateColumns);
    gridRef.current && observer.observe(gridRef.current);

    return () => {
      gridRef.current && observer.unobserve(gridRef.current);
    };
  }, []);

  // 计算默认显示的项数:2行 × 列数 - 1(留最后一格给按钮)
  const defaultShowCount = columnCount * 2 - 1;
  // 判断是否需要显示按钮(总项数超过默认显示数)
  const needShowButton = gridItemsArr.length > defaultShowCount;

  // 组装要显示的内容:折叠时显示部分项+按钮,展开时显示全部
  const displayContent = isExpanded
    ? gridItemsArr
    : needShowButton
      ? [...gridItemsArr.slice(0, defaultShowCount), { isButton: true } as unknown as GridItem]
      : gridItemsArr;

2. Grid组件样式调整

修改Grid组件的样式,根据展开状态切换行数限制:

const gridCollapsedStyles = css`
  grid-template-rows: repeat(2, 1fr);
`;

const gridExpandedStyles = css`
  grid-template-rows: repeat(auto-fit, 1fr);
`;

type GridProps = {
  isExpanded: boolean;
};

const Grid = styled.div<GridProps>`
  display: grid;
  gap: var(--spacing-xs) var(--spacing-2xs);
  grid-template-columns: repeat(auto-fit, 170px);

  ${MQ("desktop")} {
    grid-template-columns: repeat(auto-fit, 200px);
  }

  /* 根据展开状态应用不同样式 */
  ${({ isExpanded }) => isExpanded ? gridExpandedStyles : gridCollapsedStyles}
`;

3. 渲染Grid项与展开按钮

在Grid中渲染内容,区分普通项和按钮:

// 展开按钮样式,和GridItem保持视觉一致
const ExpandButton = styled.button`
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  background: #fff;
  cursor: pointer;
  font-size: 14px;
  color: #1976d2;
  transition: background-color 0.2s;

  &:hover {
    background-color: #f5f5f5;
  }
`;

return (
  <Grid ref={gridRef} isExpanded={isExpanded}>
    {displayContent.map((item, index) => {
      // 判断是否是按钮项
      if ('isButton' in item && item.isButton) {
        return (
          <ExpandButton key="expand-btn" onClick={() => setIsExpanded(true)}>
            查看更多
          </ExpandButton>
        );
      }
      return <GridItem key={item.id} title={item.title} />; // 替换成你的GridItem组件
    })}
  </Grid>
);
};

export default GridContainer;

关键细节说明

  • 动态列数计算:用ResizeObserver实时监听容器宽度变化,确保屏幕尺寸改变时自动调整列数和按钮位置
  • 边界情况处理:当总项数不超过2行容量时,自动隐藏按钮
  • 视觉一致性:展开按钮样式与GridItem保持一致,避免视觉突兀
  • 性能优化:ResizeObserver仅在容器尺寸变化时触发计算,不会频繁执行

内容的提问来源于stack exchange,提问作者ida

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:17:03