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> ); }
原理说明
- Grid Gap:容器的
gap属性是网格布局的标准间距控制方式,比每个项的margin更稳定,不会出现单个项margin变化破坏整体布局的问题。 - 动态类名:通过判断相邻项的选中状态,给当前按钮添加对应类,去掉相邻方向的边框,并设置负margin抵消gap,让选中项之间无缝贴合。
- 平滑过渡:
transition: all 0.15s确保边框和margin的变化有动画效果,提升交互体验。
内容的提问来源于stack exchange,提问作者Codemeister
相关产品推荐
相关产品推荐

