react + css 实现 椭圆布局

发布于:2025-03-28 ⋅ 阅读:(30) ⋅ 点赞:(0)

一  项目需求,按照椭圆形状布局一些元素

具体布局如下图所示:

react 代码如下:

import React, {useEffect, useRef} from 'react';
import "./index.less";

export default () => {
  const containerRef = useRef<HTMLDivElement>(null);

  // 计算周围圆点的位置。
  const calculatePosition = () => {
    if (containerRef.current) {

      const childElements = containerRef.current?.querySelectorAll('.circle');
      const longRadius = 450; // 椭圆长半轴 (900px/2)
      const shortRadius = 200; // 椭圆短半轴 (400px/2)
      const offset = 40; // 周围圆圈与椭圆的间距

      childElements.forEach((circle: any, index: number) => {
        // 计算元素的角度
        const angle = Math.PI + (index / 13) * Math.PI * 2; // 写法2: 1号元素在椭圆的左边
        let x: number = (longRadius + offset) * Math.cos(angle); // 计算元素的x轴距离
        let y: number = (shortRadius + offset) * Math.sin(angle);// 计算元素的y轴距离
        circle.style.setProperty('--x', `${x}px`); // 进行x轴偏移
        circle.style.setProperty('--y', `${y}px`);//进行y轴偏移
      });
    }
  }

  useEffect(() => {
    calculatePosition();
  }, []);


  return (
    <div id={"pageContainer"} className="page">
      <div id={"chartContainer"} className="container" ref={containerRef}>
        <div className="centerCircle"></div>
        <div className="circle">1</div>
        <div className="circle">2</div>
        <div className="circle">3</div>
        <div className="circle">4</div>
        <div className="circle">5</div>
        <div className="circle">6</div>
        <div className="circle">7</div>
        <div className="circle">8</div>
        <div className="circle">9</div>
        <div className="circle">10</div>
        <div className="circle">11</div>
        <div className="circle">12</div>
        <div className="circle">13</div>
      </div>
    </div>
  );
};




less代码如下:

.page {
  display: flex;
  justify-content: center;
  align-items: center;
}

.container {
  position: relative;
  width: 100%;
  height: calc(100vh - 240px);

}

.centerCircle {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 900px;
  height: 400px;
  background-color: #4CAF50;
  border-radius: 50%;
}

.circle {
  position: absolute;
  width: 40px;
  height: 40px;
  background-color: #ff5722;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  font-weight: bold;
  transform: translate(-50%, -50%);
  --x: 0;
  --y: 0;
  left: calc(50% + var(--x));
  top: calc(50% + var(--y));
}

功能拓展,CSS样式都不会变。下面就不再写css样式了,用上面的即可。 

二  功能拓展:以上布局,顶点1是在左边,顺时针围绕布局,现在想将顶点1放到右边,就要对angle计算进行调整。

改变前的效果如图:

改变后的效果如图:

原来的代码是:

const angle = Math.PI + (index / 13) * Math.PI * 2

调整后的代码是:

const angle: number = (index / 13) * Math.PI * 2; // 写法1:1号元素在椭圆的右边

三 功能拓展:单独对某个点位的元素进行位置调整

        如图,调整第 8个元素的位置,调整结果如下图:

        要达到以上效果,需要进行判断点位。在进行位置偏移前对x  y值进行修改。新增点位判断代码如下图所示:

        这里对 x 操作 就是进行左右偏移,对 y 轴操作,就是上下偏移

四 功能拓展:对屏幕缩放时,同时对椭圆布局的内容进行缩放,并且不改变内容的布局、位置。

        实现方式,将计算缩放比例和大小使用transform和scale方法进行调整。同时监听屏幕缩放事件,并且在计算布局时调用。计算函数如下:

五 以上所有功能的完整代码如下:

import React, {useEffect, useRef} from 'react';
import "./index.less";

export default () => {
  const containerRef = useRef<HTMLDivElement>(null);

  // 当窗口缩小时,将页面等比例缩小,这样就不会造成里面的元素错位和重叠
  const scaleDataCompDiv = () => {

    // 这里需要指定外部元素和内部元素,用于计算布局位置。
    const outer = document.getElementById('pageContainer');
    const inner = document.getElementById('chartContainer');
    if (outer && inner) {
      const outerWidth = outer.offsetWidth;
      const outerHeight = outer.offsetHeight;
      // 这里是里面内容的原始宽高,指定之后,才能根据页面的缩放进行比例的计算(这个为了比例好看,可以自定义)
      const innerWidth = 800;// 内部 div 的原始宽度
      const innerHeight = 600; // 内部 div 的原始高度

      // 计算缩放比例(以宽度或高度的较小比例为准,避免超出)
      const scale = Math.min(
        outerWidth / innerWidth,
        outerHeight / innerHeight
      );

      // 应用缩放
      inner.style.transform = `scale(${scale})`;
    }
  }

  useEffect(() => {
    // 窗口大小变化时重新计算
    window.addEventListener('resize', scaleDataCompDiv);
    return () => {
      window.removeEventListener('resize', scaleDataCompDiv);
    }
  }, []);

  // 计算周围圆点的位置。
  const calculatePosition = () => {
    if (containerRef.current) {

      const childElements = containerRef.current?.querySelectorAll('.circle');
      const longRadius = 450; // 椭圆长半轴 (900px/2)
      const shortRadius = 200; // 椭圆短半轴 (400px/2)
      const offset = 40; // 周围圆圈与椭圆的间距

      childElements.forEach((circle: any, index: number) => {
        // 计算元素的角度
        // const angle: number = (index / 13) * Math.PI * 2; // 写法1:1号元素在椭圆的右边
        const angle = Math.PI + (index / 13) * Math.PI * 2; // 写法2: 1号元素在椭圆的左边
        let x: number = (longRadius + offset) * Math.cos(angle); // 计算元素的x轴距离
        let y: number = (shortRadius + offset) * Math.sin(angle);// 计算元素的y轴距离

        // 这里对元素序号的判断,可以对 x y进行特殊调整。实现部分元素特殊位置的展示
        if (index === 7) { // 如果index是7,则,对第8个元素的位置进行特殊调整。
           x += 200; // 第8个元素向右再偏移100px的距离
         }
        circle.style.setProperty('--x', `${x}px`); // 进行x轴偏移
        circle.style.setProperty('--y', `${y}px`);//进行y轴偏移
      });

      // 需要统一缩放比例。否则会导致视觉上的差异。
      scaleDataCompDiv();
    }
  }

  useEffect(() => {
    calculatePosition();
  }, []);


  return (
    <div id={"pageContainer"} className="page">
      <div id={"chartContainer"} className="container" ref={containerRef}>
        <div className="centerCircle"></div>
        <div className="circle">1</div>
        <div className="circle">2</div>
        <div className="circle">3</div>
        <div className="circle">4</div>
        <div className="circle">5</div>
        <div className="circle">6</div>
        <div className="circle">7</div>
        <div className="circle">8</div>
        <div className="circle">9</div>
        <div className="circle">10</div>
        <div className="circle">11</div>
        <div className="circle">12</div>
        <div className="circle">13</div>
      </div>
    </div>
  );
};