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

如何在React中用CSS translateX或其他方式实现激活元素居中?

问题:激活状态圆点始终保持窗口水平居中的实现方案

需求说明

激活状态的圆点始终保持在窗口水平中心位置,其余圆点根据激活圆点自动对齐。

当前代码

import "./styles.css";
import React, { useRef, useState, useEffect } from "react";
import styles from "styled-components";

const DotsWrapper = styles.div`
  display: flex;
  justify-content: center;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
`;

const Dot = styles.div`
  width: 50px;
  height: 50px;
  text-align: center;
  padding: 2rem;
  border-radius: 50%;
  background-color: ${({ isActive }) => (isActive ? "#fc916a" : "#436073")};
  transition: transform 0.3s, background-color 0.3s;
  transform: ${({ isActive, index, activeIndex, centerIndex }) => {
    const distanceFromCenter = index - centerIndex;
    const distanceFromActive = index - activeIndex;
    if (isActive) {
      return "scale(1.3)";
    } else if (distanceFromActive === 1) {
      return "translateX(25px)";
    } else if (distanceFromActive === -1) {
      return "translateX(-25px)";
    } else if (distanceFromCenter > 0) {
      return "translateX(50px)";
    } else if (distanceFromCenter < 0) {
      return "translateX(-50px)";
    } else {
      return "scale(1)";
    }
  }};
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  font-size: 20px;
  margin: 0 10px;
`;

const CenterDot = styles(Dot)`
  transform: scale(1.3);
`;

export default function App() {
  const mobileContent = {
    page_blocks: [
      { info_date: "01-01-2002" },
      { info_date: "01-01-2003" },
      { info_date: "01-01-2004" },
      { info_date: "01-01-2005" },
      { info_date: "01-01-2006" },
      { info_date: "01-01-2007" }
    ]
  };

  const [activeIndex, setActiveIndex] = React.useState(0);
  const activeDot = React.useRef(null);

  const centerIndex = Math.floor(mobileContent.page_blocks.length / 2);
  const windowWidth = window.innerWidth;
  const centerPosition = windowWidth / 2;
  const [activePosition, setActivePosition] = React.useState(0);

  React.useEffect(() => {
    const activeDotPosition = activeDot.current.getBoundingClientRect();
    const activePosition = 
      activeDotPosition.x + activeDotPosition.width / 2 - centerPosition;
    setActivePosition(activePosition);
  }, [windowWidth]);

  const result = centerPosition - activePosition;
  const translateX = result > 0 ? `${result}px` : `-${Math.abs(result)}px`;

  return (
    <React.Fragment>
      <div
        className="line"
        style={{
          position: "relative",
          width: "100%",
          overflowX: "hidden",
          height: "350px"
        }}
      >
        <br />
        <br />
        <br />
        windowWidth = {windowWidth}
        <br />
        centerPosition = {centerPosition}
        <br />
        activePosition = {activePosition}
        <br />
        result= {result}
        <br />
        {translateX}
        <br />
        <DotsWrapper style={{ transform: `translateX(${translateX})` }}>
          {mobileContent.page_blocks.map((item, index) =>
            index === activeIndex ? (
              <CenterDot
                key={index}
                isActive
                index={index}
                centerIndex={centerIndex}
                activeIndex={activeIndex}
                ref={activeDot}
              >
                {new Date(item.info_date).getFullYear()}
              </CenterDot>
            ) : (
              <Dot
                key={index}
                isActive={index === activeIndex}
                index={index}
                centerIndex={centerIndex}
                activeIndex={activeIndex}
                onClick={() => setActiveIndex(index)}
              >
                {new Date(item.info_date).getFullYear()}
              </Dot>
            )
          )}
        </DotsWrapper>
      </div>
    </React.Fragment>
  );
}

当前效果

当前效果

尝试思路

  • 获取窗口总宽度
  • 获取窗口中心位置
  • 获取激活圆点当前位置
  • 通过计算移动距离,使用CSS translateX 实现激活圆点居中,但未达到预期效果

解决方案

修改要点

  1. 简化圆点样式:移除每个圆点自身复杂的偏移逻辑,仅保留激活状态的缩放效果,避免与容器的移动逻辑冲突。
  2. 监听窗口 resize:实时更新窗口宽度,保证窗口大小变化时居中逻辑依然有效。
  3. 修正居中计算逻辑:当激活索引切换或窗口大小变化时,重新计算容器需要移动的距离,确保激活圆点始终处于窗口中心。

修改后的完整代码

import "./styles.css";
import React, { useRef, useState, useEffect } from "react";
import styles from "styled-components";

const DotsWrapper = styles.div`
  display: flex;
  position: relative;
  transition: transform 0.3s ease; /* 添加上过渡动画 */
`;

const Dot = styles.div`
  width: 50px;
  height: 50px;
  text-align: center;
  padding: 2rem;
  border-radius: 50%;
  background-color: ${({ isActive }) => (isActive ? "#fc916a" : "#436073")};
  transition: transform 0.3s, background-color 0.3s;
  transform: ${({ isActive }) => isActive ? "scale(1.3)" : "scale(1)"};
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  font-size: 20px;
  margin: 0 10px;
  flex-shrink: 0; /* 防止圆点被压缩变形 */
  cursor: pointer;
`;

export default function App() {
  const mobileContent = {
    page_blocks: [
      { info_date: "01-01-2002" },
      { info_date: "01-01-2003" },
      { info_date: "01-01-2004" },
      { info_date: "01-01-2005" },
      { info_date: "01-01-2006" },
      { info_date: "01-01-2007" }
    ]
  };

  const [activeIndex, setActiveIndex] = useState(0);
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);
  const [wrapperTranslateX, setWrapperTranslateX] = useState(0);
  const activeDot = useRef(null);

  // 监听窗口大小变化
  useEffect(() => {
    const handleResize = () => setWindowWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  // 计算容器偏移量,保证激活圆点居中
  useEffect(() => {
    if (!activeDot.current) return;
    
    const activeDotRect = activeDot.current.getBoundingClientRect();
    const activeCenter = activeDotRect.left + activeDotRect.width / 2;
    const windowCenter = windowWidth / 2;
    
    // 容器需要移动的距离:让激活圆点的中心对齐窗口中心
    const translateX = -(activeCenter - windowCenter);
    setWrapperTranslateX(translateX);
  }, [activeIndex, windowWidth]);

  return (
    <React.Fragment>
      <div
        className="line"
        style={{
          position: "relative",
          width: "100%",
          overflowX: "hidden",
          height: "350px",
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          justifyContent: "center"
        }}
      >
        <DotsWrapper style={{ transform: `translateX(${wrapperTranslateX}px)` }}>
          {mobileContent.page_blocks.map((item, index) => (
            <Dot
              key={index}
              isActive={index === activeIndex}
              ref={index === activeIndex ? activeDot : null}
              onClick={() => setActiveIndex(index)}
            >
              {new Date(item.info_date).getFullYear()}
            </Dot>
          ))}
        </DotsWrapper>
      </div>
    </React.Fragment>
  );
}

逻辑说明

  • 窗口监听:通过resize事件实时更新窗口宽度,确保窗口大小变化时居中逻辑自动调整。
  • 偏移计算:获取激活圆点的中心位置,计算其与窗口中心的差值,通过容器的translateX反向偏移,让激活圆点始终处于窗口中心。
  • 样式优化:添加flex-shrink:0防止圆点被压缩,给容器添加上过渡动画,让切换过程更平滑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 23:22:08