React+Tailwind下,如何用CSS媒体查询合并表格单元格且不重复内容?
响应式表格单元格合并(无内容重复实现)
需求背景
使用React + Tailwind开发带输入框的表格,要求屏幕缩小时将两个单元格合并为一个,且不能重复单元格内容(输入框这类动态元素无法重复创建),拒绝采用重复DOM的方案(即示例中在一个单元格内重复渲染另一个单元格内容的方式),最终可通过window.matchMedia实现更灵活的控制,同时也提供纯CSS媒体查询的基础方案。
一、纯CSS媒体查询实现(无需JS)
这种方案通过调整表格布局,让小屏幕下两个单元格堆叠显示,视觉上合并为一个,且内容仅渲染一次。利用Tailwind的响应式类即可快速实现:
<table className="w-full table-auto"> <thead> <tr className="flex flex-col lg:flex-row"> <th className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">名称</th> <th className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 lg:pl-3 lg:border-l">职位</th> </tr> </thead> <tbody className="divide-y divide-gray-200 bg-white"> <tr className="flex flex-col lg:flex-row"> <td className="w-full py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0 lg:w-auto"> <input type="text" placeholder="输入名称" className="w-full p-2 border rounded" /> </td> <td className="w-full py-4 pl-4 pr-3 text-sm text-gray-500 lg:w-auto lg:pl-3 lg:border-l"> <input type="text" placeholder="输入职位" className="w-full p-2 border rounded" /> </td> </tr> </tbody> </table>
实现逻辑
- 在小屏幕(
lg断点以下)将<tr>设为flex-col,让单元格垂直堆叠并占满整行宽度 - 大屏幕下恢复
flex-row,单元格并排显示 - 通过Tailwind的
lg:border-l控制大屏幕下的单元格分隔线,小屏幕下自动隐藏,视觉上形成合并效果 - 输入框仅渲染一次,完全避免重复内容问题
二、window.matchMedia方案(精准控制内容位置)
如果需要在小屏幕下将第二个单元格的内容插入到第一个单元格的特定位置(比如紧跟第一个输入框之后),纯CSS无法实现跨元素内容移动,此时可通过window.matchMedia监听屏幕尺寸变化,动态调整DOM结构,同样保证内容不重复:
import { useEffect, useRef } from 'react'; const ResponsiveTable = () => { // 定义DOM引用 const nameTdRef = useRef(null); const titleTdRef = useRef(null); const titleContentRef = useRef(null); useEffect(() => { // 匹配lg断点以下的屏幕尺寸(可根据Tailwind配置修改) const mediaQuery = window.matchMedia('(max-width: 1023px)'); const handleLayoutChange = (e) => { if (e.matches) { // 小屏幕:将职位输入框移到名称单元格内 if (titleContentRef.current && nameTdRef.current) { // 添加分隔文本优化视觉效果 const separator = document.createElement('div'); separator.className = 'mt-2 text-xs text-gray-500'; separator.textContent = '职位:'; nameTdRef.current.appendChild(separator); nameTdRef.current.appendChild(titleContentRef.current); titleTdRef.current.style.display = 'none'; } } else { // 大屏幕:将职位输入框移回原单元格 if (titleContentRef.current && titleTdRef.current) { titleTdRef.current.appendChild(titleContentRef.current); titleTdRef.current.style.display = ''; // 移除小屏幕添加的分隔元素 const separator = nameTdRef.current.querySelector('.mt-2.text-xs'); if (separator) separator.remove(); } } }; // 初始加载时执行一次布局调整 handleLayoutChange(mediaQuery); // 监听屏幕尺寸变化 mediaQuery.addEventListener('change', handleLayoutChange); // 组件卸载时移除监听 return () => { mediaQuery.removeEventListener('change', handleLayoutChange); }; }, []); return ( <table className="w-full"> <thead> <tr> <th className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">名称</th> <th className="py-3.5 px-3 text-left text-sm font-semibold text-gray-900">职位</th> </tr> </thead> <tbody className="divide-y divide-gray-200 bg-white"> <tr> <td ref={nameTdRef} className="py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0"> <input type="text" placeholder="输入名称" className="w-full p-2 border rounded" /> </td> <td ref={titleTdRef} className="px-3 py-4 text-sm text-gray-500"> <div ref={titleContentRef}> <input type="text" placeholder="输入职位" className="w-full p-2 border rounded" /> </div> </td> </tr> </tbody> </table> ); }; export default ResponsiveTable;
实现逻辑
- 使用React的
useRef获取需要操作的DOM元素 - 通过
window.matchMedia监听屏幕尺寸变化,触发布局调整函数 - 小屏幕下将第二个单元格的内容容器移到第一个单元格内,同时隐藏原单元格;大屏幕下将内容移回原位置,恢复显示
- 输入框仅创建一次,完全避免重复渲染问题,同时可自定义内容在合并后的位置和样式
内容的提问来源于stack exchange,提问作者Alan Ganser
相关产品推荐
相关产品推荐

