【问题标题】:Position icons into circle将图标定位到圆圈中
【发布时间】:2021-09-10 22:18:27
【问题描述】:

如何将多个<img> 元素定位成一个围绕另一个元素的圆圈,并使这些元素也都是可点击的链接?我希望它看起来像下面的图片,但我不知道如何实现这种效果。

这可能吗?

【问题讨论】:

    标签: html css


    【解决方案1】:

    2020年解决方案

    这是我最近使用的更现代的解决方案。

    我首先从一组图像生成 HTML。是否使用 PHP、JS、一些 HTML 预处理器生成 HTML 等等……这无关紧要,因为背后的基本思想是相同的。

    这是执行此操作的 Pug 代码:

    //- start with an array of images, described by url and alt text
    - let imgs = [
    -   {
    -       src: 'image_url.jpg', 
    -       alt: 'image alt text'
    -   } /* and so on, add more images here */
    - ];
    - let n_imgs = imgs.length;
    - let has_mid = 1; /* 0 if there's no item in the middle, 1 otherwise */
    - let m = n_imgs - has_mid; /* how many are ON the circle */
    - let tan = Math.tan(Math.PI/m); /* tangent of half the base angle */
    
    .container(style=`--m: ${m}; --tan: ${+tan.toFixed(2)}`)
        - for(let i = 0; i < n_imgs; i++)
            a(href='#' style=i - has_mid >= 0 ? `--i: ${i}` : null)
              img(src=imgs[i].src alt=imgs[i].alt)
    

    生成的 HTML 如下所示(是的,您也可以手动编写 HTML,但之后进行更改会很痛苦):

    <div class="container" style="--m: 8; --tan: 0.41">
      <a href='#'>
        <img src="image_mid.jpg" alt="alt text"/>
      </a>
      <a style="--i: 1">
        <img src="first_img_on_circle.jpg" alt="alt text"/>
      </a>
      <!-- the rest of those placed on the circle -->
    </div>
    

    在 CSS 中,我们决定图像的大小,比如8em--m 项目位于一个圆上,如果它们位于由--m 边组成的多边形的边的中间,所有这些边都与圆相切。

    如果您难以想象,您可以使用interactive demo,它为您通过拖动滑块选择边数的各种多边形构造内圆和外接圆。

    这告诉我们容器的大小必须是圆半径的两倍加上图像大小的一半。

    我们还不知道半径,但是如果我们知道边的数量(因此是底角一半的正切,预先计算并设置为自定义属性--tan)和多边形边的数量,我们就可以计算它.我们可能希望多边形边缘的大小至少与图像的大小一样,但我们在边上留下多少是任意的。假设我们每边都有一半的图像大小,所以多边形边缘是图像大小的两倍。这为我们提供了以下 CSS:

    .container {
      --d: 6.5em; /* image size */
      --rel: 1; /* how much extra space we want between images, 1 = one image size */
      --r: calc(.5*(1 + var(--rel))*var(--d)/var(--tan)); /* circle radius */
      --s: calc(2*var(--r) + var(--d)); /* container size */
      position: relative;
      width: var(--s); height: var(--s);
      background: silver /* to show images perfectly fit in container */
    }
    
    .container a {
      position: absolute;
      top: 50%; left: 50%;
      margin: calc(-.5*var(--d));
      width: var(--d); height: var(--d);
      --az: calc(var(--i)*1turn/var(--m));
      transform: 
        rotate(var(--az)) 
        translate(var(--r))
        rotate(calc(-1*var(--az)))
    }
    
    img { max-width: 100% }
    

    有关转换链如何工作的说明,请参阅旧解决方案。

    这样,在图像数组中添加或删除图像会自动将新数量的图像排列在一个圆圈上,使它们等距分布,并调整容器的大小。您可以在this demo 中进行测试。


    旧解决方案(由于历史原因保留)

    是的,仅使用 CSS 就很有可能而且非常简单。您只需要清楚地记住您希望与图像链接的角度(我在末尾添加了一段代码,只是为了在您悬停其中一个时显示角度)。

    您首先需要一个包装器。我将其直径设置为24emwidth: 24em; height: 24em; 这样做),您可以将其设置为任何您想要的。你给它position: relative;

    然后,您将带有图像的链接放置在该包装的中心,水平和垂直。您可以通过设置position: absolute; 然后设置top: 50%; left: 50%;margin: -2em; 来做到这一点(其中2em 是图像链接宽度的一半,我已将其设置为4em - 再次,您可以更改它任何你想要的,但不要忘记在这种情况下更改边距)。

    然后,您决定要与图像链接的角度,并添加一个类deg{desired_angle}(例如deg0deg45 或其他)。然后为每个这样的类应用链式 CSS 转换,如下所示:

    .deg{desired_angle} {
       transform: rotate({desired_angle}) translate(12em) rotate(-{desired_angle});
    }
    

    {desired_angle} 替换为045 等等...

    第一个旋转变换旋转对象及其轴,平移变换沿旋转的 X 轴平移对象,第二个旋转变换使对象回到原位。

    这种方法的优点是灵活。您可以在不改变当前结构的情况下添加不同角度的新图像。

    代码片段

        .circle-container {
            position: relative;
            width: 24em;
            height: 24em;
            padding: 2.8em;
            /*2.8em = 2em*1.4 (2em = half the width of a link with img, 1.4 = sqrt(2))*/
            border: dashed 1px;
            border-radius: 50%;
            margin: 1.75em auto 0;
        }
        .circle-container a {
            display: block;
            position: absolute;
            top: 50%; left: 50%;
            width: 4em; height: 4em;
            margin: -2em;
        }
        .circle-container img { display: block; width: 100%; }
        .deg0 { transform: translate(12em); } /* 12em = half the width of the wrapper */
        .deg45 { transform: rotate(45deg) translate(12em) rotate(-45deg); }
        .deg135 { transform: rotate(135deg) translate(12em) rotate(-135deg); }
        .deg180 { transform: translate(-12em); }
        .deg225 { transform: rotate(225deg) translate(12em) rotate(-225deg); }
        .deg315 { transform: rotate(315deg) translate(12em) rotate(-315deg); }
        <div class='circle-container'>
            <a href='#' class='center'><img src='image.jpg'></a>
            <a href='#' class='deg0'><img src='image.jpg'></a>
            <a href='#' class='deg45'><img src='image.jpg'></a>
            <a href='#' class='deg135'><img src='image.jpg'></a>
            <a href='#' class='deg180'><img src='image.jpg'></a>
            <a href='#' class='deg225'><img src='image.jpg'></a>
            <a href='#' class='deg315'><img src='image.jpg'></a>
        </div>

    此外,您可以通过使用链接的背景图像而不是使用img 标签来进一步简化 HTML。


    编辑example with fallback for IE8 and older(在 IE8 和 IE7 中测试)

    【讨论】:

    • 很好,但是当人们从不支持 CSS 转换的设备/浏览器访问时会看到什么?
    • 唯一不支持 CSS 转换的桌面浏览器是 IE8 和更早版本。对于那些,这可以使用 IE 矩阵过滤器转换来模拟。至于移动浏览器,Opera Mini 是唯一不支持 CSS 转换的浏览器,我真的不会在小屏幕上使用如此浪费空间的东西。
    • 当我看到演示时,我向下滚动,因为我知道会是你回答这样的问题。干得好@Ana。你到底在哪里写博客?
    • @Ana 太棒了,如果有兴趣,可以使用你的 CSS 为 n 个项目制作一个通用示例。jsfiddle.net/sajjansarkar/zgcgq8cg
    • @Ana 很酷!你启发了我创建一个动态版本 - jsfiddle.net/skwidbreth/q59s90oy
    【解决方案2】:

    这是没有绝对定位的简单解决方案:

    .container .row {
      margin: 20px;
      text-align: center;
    }
    
    .container .row img {
      margin: 0 20px;
    }
    <div class="container">
      <div class="row">
        <img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
        <img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
      </div>
      <div class="row">
        <img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
        <img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
        <img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
      </div>
      <div class="row">
        <img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
        <img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
      </div>
    </div>

    http://jsfiddle.net/mD6H6/

    【讨论】:

      【解决方案3】:

      使用@Ana提出的解决方案:

      transform: rotate(${angle}deg) translate(${radius}px) rotate(-${angle}deg)
      

      我创建了以下jsFiddle,它使用纯 JavaScript(也提供 jQuery 版本)动态放置圆圈。

      它的工作方式相当简单:

      document.querySelectorAll( '.ciclegraph' ).forEach( ( ciclegraph )=>{
        let circles = ciclegraph.querySelectorAll( '.circle' )
        let angle = 360-90, dangle = 360 / circles.length
        for( let i = 0; i < circles.length; ++i ){
          let circle = circles[i]
          angle += dangle
          circle.style.transform = `rotate(${angle}deg) translate(${ciclegraph.clientWidth / 2}px) rotate(-${angle}deg)`
        }
      })
      .ciclegraph {
        position: relative;
        width: 500px;
        height: 500px;
        margin: calc(100px / 2 + 0px);
      }
      
      .ciclegraph:before {
        content: "";
        position: absolute;
        top: 0; left: 0;
        border: 2px solid teal;
        width: calc( 100% - 2px * 2);
        height: calc( 100% - 2px * 2 );
        border-radius: 50%;
      }
      
      .ciclegraph .circle {
        position: absolute;
        top: 50%; left: 50%;
        width: 100px;
        height: 100px;
        margin: calc( -100px / 2 );
        background: teal;
        border-radius: 50%;
      }
      <div class="ciclegraph">
        <div class="circle"></div>
        <div class="circle"></div>
        <div class="circle"></div>
        <div class="circle"></div>
        <div class="circle"></div>
        <div class="circle"></div>
      </div>

      【讨论】:

      • 如果这能够添加动态,这将是正确的答案。
      【解决方案4】:

      在@Ana 的出色回答的基础上,我创建了这个动态版本,允许您在 DOM 中添加和删除元素并保持元素之间的比例间距 - 看看我的小提琴:https://jsfiddle.net/skwidbreth/q59s90oy/

      var list = $("#list");
      
      var updateLayout = function(listItems) {
        for (var i = 0; i < listItems.length; i++) {
          var offsetAngle = 360 / listItems.length;
          var rotateAngle = offsetAngle * i;
          $(listItems[i]).css("transform", "rotate(" + rotateAngle + "deg) translate(0, -200px) rotate(-" + rotateAngle + "deg)")
        };
      };
      
      $(document).on("click", "#add-item", function() {
        var listItem = $("<li class='list-item'>Things go here<button class='remove-item'>Remove</button></li>");
        list.append(listItem);
        var listItems = $(".list-item");
        updateLayout(listItems);
      
      });
      
      $(document).on("click", ".remove-item", function() {
        $(this).parent().remove();
        var listItems = $(".list-item");
        updateLayout(listItems);
      });
      #list {
        background-color: blue;
        height: 400px;
        width: 400px;
        border-radius: 50%;
        position: relative;
      }
      
      .list-item {
        list-style: none;
        background-color: red;
        height: 50px;
        width: 50px;
        position: absolute;
        top: 50%;
        left: 50%;
      }
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
      
      <ul id="list"></ul>
      <button id="add-item">Add item</button>

      【讨论】:

      • 效果很好,如果可以的话,我会支持更多。我遇到的一个问题是,如果我将 360 更改为其他任何东西(我想要一个半圆),事情就会变得不正常。我将其追溯到旋转角度的声明并将其更改为 var rotateAngle = zero_start + (offsetAngle * i || 0); 我还为 zero_start 添加了一个变量,因此如果您想从点 270 而不是 0 开始,或者类似的东西。 jsfiddle.net/q59s90oy/13。最后,我将列表项的 css 更改为使用负边距。 说真的,感谢分享工作,帮了大忙。
      • 太棒了,很高兴您能够根据需要对其进行调整。不错的变化!
      • 哟,这是一个非常史诗般的螺旋效果? i.imgur.com/1VrubKC.png
      • @Ethan 哈哈是的!我喜欢这样做!我认为它可以成为一件很酷的艺术品。
      • 这很好,但是当它从 0 开始并做 360 度时,添加的 div 在水平中心左侧和右侧向内移动(向内漂移)。顶部和底部添加的 div 中心与列表圆圈对齐。
      【解决方案5】:

      这是我根据此处的示例在 React 中制作的版本。

      CodeSandbox Example

      import React, { useRef, useEffect } from "react";
      
      import "./styles.css";
      
      export default function App() {
        const graph = useRef(null);
      
        useEffect(() => {
          const ciclegraph = graph.current;
          const circleElements = ciclegraph.childNodes;
      
          let angle = 360 - 90;
          let dangle = 360 / circleElements.length;
      
          for (let i = 0; i < circleElements.length; i++) {
            let circle = circleElements[i];
            angle += dangle;
            circle.style.transform = `rotate(${angle}deg) translate(${ciclegraph.clientWidth /
              2}px) rotate(-${angle}deg)`;
          }
        }, []);
      
        return (
          <div className="App">
            <div className="ciclegraph" ref={graph}>
              <div className="circle" />
              <div className="circle" />
              <div className="circle" />
              <div className="circle" />
              <div className="circle" />
              <div className="circle" />
            </div>
          </div>
        );
      }
      

      【讨论】:

      • 很棒的答案和很棒的代码,唯一的问题是您将它发布在与 React 无关的答案上!
      • 我知道,没有人要求的答案,但不管怎样,嘿嘿:)
      • 我来这里是为了寻找可以在 React 中使用的解决方案,所以仍然非常有用
      【解决方案6】:

      您当然可以使用纯 CSS 或 JavaScript 来实现。我的建议:

      • 如果您已经知道图像数量永远不会改变,只需计算您的样式并使用普通 css(优点:性能更好,非常可靠)

      • 如果数字可以在您的应用中动态变化,或者将来可能会发生变化,请使用 Js 解决方案(优点:更具前瞻性)

      我有类似的工作要做,所以我创建了一个脚本并将其开源here on Github 供任何可能需要它的人使用。它只接受一些配置值并简单地输出您需要的 CSS 代码。

      如果您想选择 Js 解决方案,这里有一个对您有用的简单指针。使用此 html 作为起点,将 #box 容器和 .dot 中间的图像/div 用作您想要所有其他图像的起点:

      起始html:

          <div id="box">
            <div class="dot"></div>
            <img src="my-img.jpg">
            <!-- all the other images you need-->
          </div>
      

      开始CSS:

           #box{
            width: 400px;
            height: 400px;
            position: relative;
            border-radius: 100%;
            border: 1px solid teal;
          }
      
          .dot{
              position: absolute;
              border-radius: 100%;
              width: 40px;
              height: 40px;
              left: 50%;
              top: 50%;
              margin-left: -20px;
              margin-top: -20px;
              background: rebeccapurple;
          }
          img{
            width: 40px;
            height: 40px;
            position: absolute;
          }
      

      您可以按照以下方式创建快速函数:

          var circle = document.getElementById('box'),
              imgs = document.getElementsByTagName('img'),
              total = imgs.length,
              coords = {},
              diam, radius1, radius2, imgW;
          
          // get circle diameter
          // getBoundingClientRect outputs the actual px AFTER transform
          //      using getComputedStyle does the job as we want
          diam = parseInt( window.getComputedStyle(circle).getPropertyValue('width') ),
          radius = diam/2,
          imgW = imgs[0].getBoundingClientRect().width,
          // get the dimensions of the inner circle we want the images to align to
          radius2 = radius - imgW
          
          var i,
              alpha = Math.PI / 2,
              len = imgs.length,
              corner = 2 * Math.PI / total;
          
          // loop over the images and assign the correct css props
          for ( i = 0 ; i < total; i++ ){
          
            imgs[i].style.left = parseInt( ( radius - imgW / 2 ) + ( radius2 * Math.cos( alpha ) ) ) + 'px'
            imgs[i].style.top =  parseInt( ( radius - imgW / 2 ) - ( radius2 * Math.sin( alpha ) ) ) + 'px'
          
            alpha = alpha - corner;
          }
      

      你可以看到一个活生生的例子here

      【讨论】:

        【解决方案7】:

        没有办法使用 CSS 神奇地将可点击的项目放置在另一个元素周围的圆圈中。 我将如何做到这一点的方法是使用带有position:relative; 的容器。然后使用position:absolute; 放置所有元素,并使用topleft 定位它的位置。

        即使您没有在标签中放置,最好使用jQuery / javascript。

        第一步是使用position:relative; 将您的中心图像完美地放置在容器的中心。

        #centerImage {
          position:absolute;
          top:50%;
          left:50%;
          width:200px;
          height:200px;
          margin: -100px 0 0 -100px;
        }
        

        之后,您可以使用 centerImage 的 offset() 减去容器的 offset() 来放置其他元素。为您提供图像的确切 topleft

        var left = $('#centerImage').offset().left - $('#centerImage').parent().offset().left;
        var top = $('#centerImage').offset().top - $('#centerImage').parent().offset().top;
        
        $('#surroundingElement1').css({
          'left': left - 50,
          'top': top - 50 
        });
        
        $('#surroundingElement2').css({
          'left': left - 50,
          'top': top 
        });
        
        $('#surroundingElement3').css({
          'left': left - 50,
          'top': top + 50 
        });
        

        我在这里所做的是将元素 relative 放置到 centerImage。希望这会有所帮助。

        【讨论】:

          【解决方案8】:

          你可以这样做:fiddle

          不要介意定位,这是一个简单的例子

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-04-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多