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

Tailwind CSS实现水平线两端十字图标垂直居中布局问题

解决带两端十字图标的水平线布局错位问题

需求描述

需要实现以下布局效果:

  • 水平线铺满容器宽度,不超出容器边缘
  • 两端的十字SVG图标与水平线垂直居中对齐
  • 线条不会超出十字图标向左右延伸

当前问题

尝试多种方法后仍存在布局错位:线条长度不符合预期(过短或过长),图标无法与线条垂直居中对齐。

相关代码

Hero.jsx

import "./text.css";
import stars from "./stars.json";
import { SparklesIcon } from "lucide-react";
import Lottie from "lottie-react";
import Section from "./Section";
import { BottomLine } from "./design/Hero";

function Hero() {
  return (
    <Section
      className="pt-[12rem] -mt-[5.25rem]"
      crosses={true}
      crossesOffset="lg:translate-y-[5.25rem]"
      customPaddings
      id="hero"
    >
      <div className="relative container max-w-4xl text-center mx-auto mt-20">
        <div className="mx-auto inline-flex items-center justify-center font-inter text-white border-[#1e1e1e] bg-[#1e1e1e]/40 border px-4 py-2 rounded-xl">
          <SparklesIcon
            fill="#1e1e1e"
            className="w-5 h-5 text-[#dee34e] mr-2"
          />
          Our New AI Feature Has Just Returned
        </div>

        <h1 className="mt-5 text-5xl lg:text-[80px] font-semibold text-white font-inter">
          <span className="bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 text-transparent bg-clip-text">
            Supercharge
          </span>
          <br /> your journey
        </h1>

        <p className="mt-5 mb-8 text-lg text-[#ebebeb] font-inter max-w-sm mx-auto">
          Walk, run, transport better with knowledge of areas with a high and
          low concentration.
        </p>

        <a
          className="px-6 py-3 text-xl font-semibold text-black bg-white rounded-xl font-inter hover:opacity-90 transition-opacity"
          href="#"
        >
          Use BMap for Free
        </a>

        <div
          className="absolute inset-0 flex justify-center items-center pointer-events-none"
          aria-hidden="true"
        >
          <div className="relative w-[500px] h-[500px] rounded-full bg-gradient-to-r from-purple-500 via-pink-500 to-blue-500 opacity-60 blur-3xl animate-pulse"></div>
        </div>
      </div>
      

      <BottomLine />
    </Section>
  );
}

export default Hero;

Section.jsx

import SectionSvg from "../assets/svg/SectionSvg";

const Section = ({
  className,
  id,
  crosses,
  crossesOffset,
  customPaddings,
  children,
}) => {
  return (
    <div
      id={id}
      className={`
      relative max-w-6xl mx-auto 
      ${customPaddings || `py-10 lg:py-16 xl:py-20 ${crosses ? "lg:py-32 xl:py-40" : ""}`} 
      ${className || ""}`}
    >
      {children}

      <div className="hidden absolute top-0 left-5 w-0.25 h-full bg-stroke-1 pointer-events-none md:block lg:left-7.5 xl:left-10" />
      <div className="hidden absolute top-0 right-5 w-0.25 h-full bg-stroke-1 pointer-events-none md:block lg:right-7.5 xl:right-10" />

      {crosses && (
        <>
          <div
            className={`hidden absolute top-0 left-7.5 right-7.5 h-0.25 bg-stroke-1 ${crossesOffset && crossesOffset} pointer-events-none lg:block xl:left-10 right-10`}
          />
          <SectionSvg crossesOffset={crossesOffset} />
        </>
      )}
    </div>
  );
};

export default Section;

底部线条相关代码(design/Hero.js)

import { useEffect, useState } from "react";
import { MouseParallax } from "react-just-parallax";

import PlusSvg from "../../assets/svg/PlusSvg";

export const Gradient = () => {
  return (
    <>
      <div className="relative z-1 h-6 mx-2.5 bg-n-11 shadow-xl rounded-b-[1.25rem] lg:h-6 lg:mx-8" />
      <div className="relative z-1 h-6 mx-6 bg-n-11/70 shadow-xl rounded-b-[1.25rem] lg:h-6 lg:mx-20" />
    </>
  );
};

export const BottomLine = () => {
  return (
    <>
      <div className="hidden absolute top-[55.25rem] left-10 right-10 h-0.25 bg-n-6 pointer-events-none xl:block" />

      <PlusSvg className="hidden absolute top-[54.9375rem] left-[2.1875rem] z-2 pointer-events-none xl:block" />

      <PlusSvg className="hidden absolute top-[54.9375rem] right-[2.1875rem] z-2 pointer-events-none xl:block" />
    </>
  );
};

const Rings = () => {
  return (
    <>
      <div className="absolute top-1/2 left-1/2 w-[65.875rem] aspect-square border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2" />
      <div className="absolute top-1/2 left-1/2 w-[51.375rem] aspect-square border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2" />
      <div className="absolute top-1/2 left-1/2 w-[36.125rem] aspect-square border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2" />
      <div className="absolute top-1/2 left-1/2 w-[23.125rem] aspect-square border border-n-2/10 rounded-full -translate-x-1/2 -translate-y-1/2" />
    </>
  );
};

export const BackgroundCircles = ({ parallaxRef }) => {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  return (
    <div className="absolute -top-[42.375rem] left-1/2 w-[78rem] aspect-square border border-n-2/5 rounded-full -translate-x-1/2 md:-top-[38.5rem] xl:-top-[32rem]">
      <Rings />

      {/* Moving background colored circle balls */}
      <MouseParallax strength={0.07} parallaxContainerRef={parallaxRef}>
        <div className="absolute bottom-1/2 left-1/2 w-0.25 h-1/2 origin-bottom rotate-[46deg]">
          <div
            className={`w-2 h-2 -ml-1 -mt-36 bg-gradient-to-b from-[#DD734F] to-[#1A1A32] rounded-full transition-transform duration-500 ease-out ${mounted ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"}`}
          />
        </div>

        <div className="absolute bottom-1/2 left-1/2 w-0.25 h-1/2 origin-bottom -rotate-[56deg]">
          <div
            className={`w-4 h-4 -ml-1 -mt-32 bg-gradient-to-b from-[#DD734F] to-[#1A1A32] rounded-full transition-transform duration-500 ease-out ${mounted ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"}`}
          />
        </div>

        <div className="absolute bottom-1/2 left-1/2 w-0.25 h-1/2 origin-bottom rotate-[54deg]">
          <div
            className={`hidden w-4 h-4 -ml-1 mt-[12.9rem] bg-gradient-to-b from-[#B9AEDF] to-[#1A1A32] rounded-full xl:block transit transition-transform duration-500 ease-out ${mounted ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"}`}
          />
        </div>

        <div className="absolute bottom-1/2 left-1/2 w-0.25 h-1/2 origin-bottom -rotate-[65deg]">
          <div
            className={`w-3 h-3 -ml-1.5 mt-52 bg-gradient-to-b from-[#B9AEDF] to-[#1A1A32] rounded-full transition-transform duration-500 ease-out ${mounted ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"}`}
          />
        </div>

        <div className="absolute bottom-1/2 left-1/2 w-0.25 h-1/2 origin-bottom -rotate-[85deg]">
          <div
            className={`w-6 h-6 -ml-3 -mt-3 bg-gradient-to-b from-[#88E5BE] to-[#1A1A32] rounded-full transition-transform duration-500 ease-out ${mounted ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"}`}
          />
        </div>

        <div className="absolute bottom-1/2 left-1/2 w-0.25 h-1/2 origin-bottom rotate-[70deg]">
          <div
            className={`w-6 h-6 -ml-3 -mt-3 bg-gradient-to-b from-[#88E5BE] to-[#1A1A32] rounded-full transition-transform duration-500 ease-out ${mounted ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"}`}
          />
        </div>
      </MouseParallax>
    </div>
  );
};

PlusSvg.jsx

const PlusSvg = ({ className = "" }) => {
    return (
      <svg className={`${className} || ""`} width="11" height="11" fill="none">
        <path
          d="M7 1a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v2a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V8a1 1 0 0 1 1-1h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H8a1 1 0 0 1-1-1V1z"
          fill="#ada8c4"
        />
      </svg>
    );
  };
  
  export default PlusSvg;

SectionSvg.jsx

import PlusSvg from "./PlusSvg";

const SectionSvg = ({ crossesOffset }) => {
  return (
    <>
      <PlusSvg
        className={`hidden absolute -top-[0.3125rem] left-[1.5625rem] ${crossesOffset && crossesOffset} pointer-events-none lg:block xl:left-[2.1875rem]`}
      />

      <PlusSvg
        className={`hidden absolute  -top-[0.3125rem] right-[1.5625rem] ${crossesOffset && crossesOffset} pointer-events-none lg:block xl:right-[2.1875rem]`}
      />
    </>
  );
};

export default SectionSvg;

解决方案

核心问题分析

当前实现使用绝对定位分别放置线条和图标,通过固定数值调整位置,这种方式无法自适应容器变化,且容易出现对齐偏差。需要改用弹性布局,让线条和图标形成一个整体,确保对齐和长度适配。

修改BottomLine组件

替换原有的绝对定位实现,使用flex布局实现线条和图标的统一对齐:

export const BottomLine = () => {
  return (
    <div className="hidden absolute bottom-0 left-10 right-10 flex items-center pointer-events-none xl:block">
      <PlusSvg className="z-2" />
      <div className="flex-1 h-0.25 bg-n-6 mx-2" />
      <PlusSvg className="z-2" />
    </div>
  );
};
  • flex items-center确保图标与线条垂直居中
  • flex-1让中间的线条自动填充剩余空间,保证线条长度适配容器且不超出图标
  • 移除固定top数值,改为bottom-0(可根据需求调整垂直位置,或通过父容器padding控制)

调整Section组件中的顶部线条实现

同样修改顶部的线条和图标布局,避免固定定位偏差:

{crosses && (
  <>
    <div className={`hidden absolute top-0 left-10 right-10 flex items-center ${crossesOffset} pointer-events-none lg:block`}>
      <PlusSvg className="z-2" />
      <div className="flex-1 h-0.25 bg-stroke-1 mx-2" />
      <PlusSvg className="z-2" />
    </div>
  </>
)}

同时删除原有的SectionSvg组件调用,因为线条和图标已整合在同一个flex容器中。

优化PlusSvg组件

移除多余逻辑,确保className正常传递:

const PlusSvg = ({ className = "" }) => {
  return (
    <svg className={className} width="11" height="11" fill="none">
      <path
        d="M7 1a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v2a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V8a1 1 0 0 1 1-1h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H8a1 1 0 0 1-1-1V1z"
        fill="#ada8c4"
      />
    </svg>
  );
};

export default PlusSvg;

效果说明

  • flex布局让线条和图标作为整体,自动实现垂直居中,线条长度自适应容器且不超出图标范围
  • 移除固定像素位置值,避免不同屏幕尺寸下的错位
  • 保留原有的响应式控制(hidden xl:block),确保在指定屏幕尺寸下显示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 04:57:04