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

React中9×9网格选中相邻项聚合效果实现求助

解决方案:用Grid Gap+相邻选择逻辑实现选中项聚合

核心思路

放弃用margin控制网格间距,改用Grid容器的gap属性统一管理,再通过判断相邻选中项的状态,动态调整边框和负margin来实现聚合效果——既能保留初始的网格间距,又能让相邻选中项无缝贴合。

步骤1:修改基础CSS

把原有的项margin替换为容器的gap,同时调整过渡属性支持边框和margin的平滑变化:

/* 网格容器 */
.keenboard {
  display: grid;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  grid-template-columns: repeat(var(--grid-size), 1fr);
  gap: 3px; /* 替换margin,设置你需要的网格间距 */
}

/* 网格项按钮 */
.keensquare {
  width: 60px;
  height: 60px;
  border: 1px solid rgb(153, 153, 153);
  background-color: rgb(255,255,255);
  margin: 0;
  transition: all 0.15s; /* 扩展过渡范围,包含边框和margin变化 */
}

/* 选中项样式(自定义) */
.keensquare.selected {
  background-color: #e0e0e0;
}

/* 相邻选中项的样式调整 */
.keensquare.has-right-selected {
  border-right: none;
  margin-right: -3px; /* 抵消gap的间距,数值和gap一致 */
}
.keensquare.has-left-selected {
  border-left: none;
}
.keensquare.has-bottom-selected {
  border-bottom: none;
  margin-bottom: -3px; /* 抵消gap的间距,数值和gap一致 */
}
.keensquare.has-top-selected {
  border-top: none;
}

步骤2:React逻辑实现

在组件中跟踪选中项的坐标,给每个按钮判断上下左右是否有相邻选中项,动态添加对应的类:

import { useState } from 'react';

// 可自定义网格大小和间距
const GRID_SIZE = 9;
const GAP_SIZE = 3;

export default function KeenBoard() {
  // 用Set存储选中项的坐标键(格式:"行-列")
  const [selectedSquares, setSelectedSquares] = useState(new Set());

  // 切换选中状态
  const toggleSquare = (row, col) => {
    const key = `${row}-${col}`;
    const newSelected = new Set(selectedSquares);
    newSelected.has(key) ? newSelected.delete(key) : newSelected.add(key);
    setSelectedSquares(newSelected);
  };

  // 判断指定方向是否有相邻选中项
  const hasAdjacentSelected = (row, col, direction) => {
    let adjacentKey;
    switch (direction) {
      case 'right': adjacentKey = `${row}-${col + 1}`; break;
      case 'left': adjacentKey = `${row}-${col - 1}`; break;
      case 'bottom': adjacentKey = `${row + 1}-${col}`; break;
      case 'top': adjacentKey = `${row - 1}-${col}`; break;
      default: return false;
    }
    return selectedSquares.has(adjacentKey);
  };

  return (
    <div 
      className="keenboard" 
      style={{ '--grid-size': GRID_SIZE, gap: `${GAP_SIZE}px` }}
    >
      {Array.from({ length: GRID_SIZE }).map((_, row) =>
        Array.from({ length: GRID_SIZE }).map((_, col) => {
          const key = `${row}-${col}`;
          const isSelected = selectedSquares.has(key);
          
          // 组装当前按钮的类名
          const classes = ['keensquare'];
          if (isSelected) classes.push('selected');
          if (hasAdjacentSelected(row, col, 'right')) classes.push('has-right-selected');
          if (hasAdjacentSelected(row, col, 'left')) classes.push('has-left-selected');
          if (hasAdjacentSelected(row, col, 'bottom')) classes.push('has-bottom-selected');
          if (hasAdjacentSelected(row, col, 'top')) classes.push('has-top-selected');

          return (
            <button
              key={key}
              className={classes.join(' ')}
              onClick={() => toggleSquare(row, col)}
            />
          );
        })
      )}
    </div>
  );
}

原理说明

  1. Grid Gap:容器的gap属性是网格布局的标准间距控制方式,比每个项的margin更稳定,不会出现单个项margin变化破坏整体布局的问题。
  2. 动态类名:通过判断相邻项的选中状态,给当前按钮添加对应类,去掉相邻方向的边框,并设置负margin抵消gap,让选中项之间无缝贴合。
  3. 平滑过渡:transition: all 0.15s确保边框和margin的变化有动画效果,提升交互体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 12:54:23