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

如何用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布局+动态偏移量(最贴合现有结构)

通过计算选中项左侧元素的总宽度,动态调整父容器的内边距,让选中项刚好对齐容器中心。

  1. 给每个Circle组件添加Ref:用来获取每个元素的实际渲染宽度
  2. 监听选中状态变化:计算选中项左侧所有元素的总宽度
  3. 动态设置父容器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(最简单粗暴)

让选中项脱离文档流,直接定位到容器中心,其余项正常流式排列,通过父容器内边距避免重叠。

  1. 父容器设置相对定位:作为选中项绝对定位的参照物
  2. 选中项添加定位类:用Tailwind的absolute left-1/2 -translate-x-1/2实现居中,同时设置z-index避免被遮挡
  3. 父容器添加左右内边距:留出足够空间容纳两侧的未选中项

修改后的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的列布局特性,让选中项固定在中心列,其余项自动填充到左右两侧。

  1. 父容器设置Grid布局:grid-auto-flow: column让项横向排列
  2. 动态设置选中项的Grid列位置:根据总项数计算中心列索引,给选中项添加grid-column属性
  3. 设置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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 18:55:00