如何用Tailwind CSS实现行内选中项居中,其余项保持原顺序
实现选中项居中的布局思路
问题背景
需要让横向排列的Circle组件中,选中项始终处于父容器的中心位置,其余项保持原有顺序分布在选中项左右两侧,当前基于Tailwind的Flex布局无法实现该效果。
现有代码
<div className="flex mx-auto justify-center items-center"> <Circle selected={selected === ActionOptions.NONE} onClick={e => setSelected(ActionOptions.NONE)} title="None" /> <Circle selected={selected === ActionOptions.ICON} onClick={e => setSelected(ActionOptions.ICON)} title="Icon"> {Icon(Icons.CHECK, selected === ActionOptions.ICON ? 'w-20 h-20' : 'w-8 h-8')} </Circle> <Circle selected={selected === ActionOptions.EMOJI} onClick={e => setSelected(ActionOptions.EMOJI)} title="Emoji"> {Icon(Icons.SMILE, selected === ActionOptions.EMOJI ? 'w-20 h-20' : 'w-8 h-8')} </Circle> <Circle selected={selected === ActionOptions.TIMER} onClick={e => setSelected(ActionOptions.TIMER)} title="Timer"> <ClockIcon className={selected === ActionOptions.TIMER ? 'w-20 h-20' : 'w-8 h-8'} /> </Circle> </div> function Circle({ selected, children, onClick, title, }: PropsWithChildren<{ selected: boolean; onClick: (e) => void; title: string }>) { return ( <div className="flex flex-col items-center justify-center cursor-pointer"> <div onClick={onClick} className={`rounded-full flex justify-center items-center ${ selected ? 'w-32 h-32 border-4' : 'w-14 h-14 border-2' } mx-2 border-white bg-white/10`} > {children} </div> <span className="text-xs">{title}</span> </div> ); }
方案一:Flex布局+动态偏移量(最贴合现有结构)
通过计算选中项左侧元素的总宽度,动态调整父容器的内边距,让选中项刚好对齐容器中心。
- 给每个Circle组件添加Ref:用来获取每个元素的实际渲染宽度
- 监听选中状态变化:计算选中项左侧所有元素的总宽度
- 动态设置父容器padding-left:公式为
(容器宽度 - 选中项宽度)/2 - 左侧元素总宽度,确保选中项居中
修改后的核心代码:
import { useRef, useState, useEffect } from 'react'; // 父组件 const CircleGroup = () => { const [selected, setSelected] = useState(ActionOptions.NONE); const circleRefs = useRef<(HTMLDivElement | null)[]>([]); const containerRef = useRef<HTMLDivElement>(null); useEffect(() => { if (!containerRef.current) return; const containerWidth = containerRef.current.offsetWidth; const selectedIndex = Object.values(ActionOptions).indexOf(selected); // 计算左侧元素总宽度 let leftTotal = 0; for (let i = 0; i < selectedIndex; i++) { circleRefs.current[i] && (leftTotal += circleRefs.current[i].offsetWidth); } // 计算选中项宽度 const selectedWidth = circleRefs.current[selectedIndex]?.offsetWidth || 0; // 计算需要的偏移量 const paddingLeft = (containerWidth - selectedWidth) / 2 - leftTotal; containerRef.current.style.paddingLeft = `${Math.max(0, paddingLeft)}px`; }, [selected]); return ( <div ref={containerRef} className="flex mx-auto items-center overflow-x-auto" > <Circle ref={el => circleRefs.current[0] = el} selected={selected === ActionOptions.NONE} onClick={() => setSelected(ActionOptions.NONE)} title="None" /> <Circle ref={el => circleRefs.current[1] = el} selected={selected === ActionOptions.ICON} onClick={() => setSelected(ActionOptions.ICON)} title="Icon" > {Icon(Icons.CHECK, selected === ActionOptions.ICON ? 'w-20 h-20' : 'w-8 h-8')} </Circle> <Circle ref={el => circleRefs.current[2] = el} selected={selected === ActionOptions.EMOJI} onClick={() => setSelected(ActionOptions.EMOJI)} title="Emoji" > {Icon(Icons.SMILE, selected === ActionOptions.EMOJI ? 'w-20 h-20' : 'w-8 h-8')} </Circle> <Circle ref={el => circleRefs.current[3] = el} selected={selected === ActionOptions.TIMER} onClick={() => setSelected(ActionOptions.TIMER)} title="Timer" > <ClockIcon className={selected === ActionOptions.TIMER ? 'w-20 h-20' : 'w-8 h-8'} /> </Circle> </div> ); }; // 修改Circle组件支持Ref const Circle = React.forwardRef<HTMLDivElement, PropsWithChildren<{ selected: boolean; onClick: () => void; title: string }>>(({ selected, children, onClick, title }, ref) => { return ( <div ref={ref} className="flex flex-col items-center justify-center cursor-pointer"> <div onClick={onClick} className={`rounded-full flex justify-center items-center ${ selected ? 'w-32 h-32 border-4' : 'w-14 h-14 border-2' } mx-2 border-white bg-white/10`} > {children} </div> <span className="text-xs">{title}</span> </div> ); });
方案二:绝对定位+Transform(最简单粗暴)
让选中项脱离文档流,直接定位到容器中心,其余项正常流式排列,通过父容器内边距避免重叠。
- 父容器设置相对定位:作为选中项绝对定位的参照物
- 选中项添加定位类:用Tailwind的
absolute left-1/2 -translate-x-1/2实现居中,同时设置z-index避免被遮挡 - 父容器添加左右内边距:留出足够空间容纳两侧的未选中项
修改后的Circle组件和父容器:
// Circle组件 function Circle({ selected, children, onClick, title, }: PropsWithChildren<{ selected: boolean; onClick: (e) => void; title: string }>) { return ( <div className={`flex flex-col items-center justify-center cursor-pointer ${ selected ? 'absolute left-1/2 -translate-x-1/2 z-10' : '' }`}> <div onClick={onClick} className={`rounded-full flex justify-center items-center ${ selected ? 'w-32 h-32 border-4' : 'w-14 h-14 border-2' } mx-2 border-white bg-white/10`} > {children} </div> <span className="text-xs">{title}</span> </div> ); } // 父容器 <div className="flex mx-auto justify-center items-center relative py-8 px-32"> {/* 原有Circle组件 */} </div>
方案三:CSS Grid布局(更优雅的现代方案)
利用Grid的列布局特性,让选中项固定在中心列,其余项自动填充到左右两侧。
- 父容器设置Grid布局:
grid-auto-flow: column让项横向排列 - 动态设置选中项的Grid列位置:根据总项数计算中心列索引,给选中项添加
grid-column属性 - 设置Grid对齐方式:
justify-items: center确保所有项在列内居中
示例代码:
const totalItems = Object.values(ActionOptions).length; const centerColumn = Math.ceil(totalItems / 2); // 父容器 <div className="grid grid-auto-flow-column mx-auto justify-items-center"> {Object.values(ActionOptions).map((option, index) => ( <Circle key={option} selected={selected === option} onClick={() => setSelected(option)} title={option} style={selected === option ? { gridColumn: centerColumn } : {}} > {/* 对应图标内容 */} </Circle> ))} </div>
内容的提问来源于stack exchange,提问作者GrandMassive
相关产品推荐
相关产品推荐

