Định vị các biểu tượng vào vòng tròn


96

Làm cách nào tôi có thể định vị một số <img>phần tử thành một vòng tròn xung quanh một phần tử khác và tất cả các phần tử đó cũng là liên kết có thể nhấp được? Tôi muốn nó trông giống như hình dưới đây, nhưng tôi không biết làm thế nào để đạt được hiệu ứng đó.

Kết quả như ý

Điều này thậm chí có thể?

Câu trả lời:


194

Giải pháp năm 2020

Đây là một giải pháp hiện đại hơn mà tôi sử dụng ngày nay.

Tôi bắt đầu bằng cách tạo HTML bắt đầu từ một mảng hình ảnh. Cho dù HTML được tạo bằng PHP, JS, một số bộ tiền xử lý HTML nào đó ... thì điều này ít quan trọng hơn vì ý tưởng cơ bản đằng sau là như nhau.

Đây là mã Pug sẽ làm điều này:

//- 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 được tạo trông như sau (và vâng, bạn cũng có thể viết HTML theo cách thủ công, nhưng sẽ rất khó để thực hiện các thay đổi sau đó):

<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>

Trong CSS, chúng tôi quyết định kích thước cho hình ảnh 8em. Các --mmục được đặt trên một vòng tròn và nếu chúng nằm ở giữa các cạnh của đa giác các --mcạnh, tất cả đều tiếp tuyến với hình tròn.

Nếu bạn gặp khó khăn trong việc hình dung điều đó, bạn có thể chơi với bản trình diễn tương tác này để tạo hình tròn và đường tròn ngoại tiếp cho các đa giác khác nhau có số cạnh bạn chọn bằng cách kéo thanh trượt.

đường tròn và đường tròn ngoại tiếp của một hình lục giác

Điều này cho chúng ta biết rằng kích thước của vùng chứa phải gấp đôi bán kính của hình tròn cộng với hai lần một nửa kích thước của hình ảnh.

Chúng tôi chưa biết bán kính, nhưng chúng tôi có thể tính toán nó nếu chúng tôi biết số cạnh (và do đó tiếp tuyến của một nửa góc cơ sở, được tính toán trước và đặt làm thuộc tính tùy chỉnh --tan) và cạnh đa giác. Chúng ta có thể muốn cạnh đa giác có kích thước nhỏ nhất của hình ảnh, nhưng chúng ta để lại bao nhiêu cạnh là tùy ý. Giả sử chúng ta có một nửa kích thước hình ảnh mỗi bên, vì vậy cạnh đa giác gấp đôi kích thước hình ảnh. Điều này cung cấp cho chúng tôi CSS sau:

.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% }

Xem giải pháp cũ để biết giải thích về cách hoạt động của chuỗi biến đổi.

Bằng cách này, việc thêm hoặc xóa hình ảnh khỏi mảng hình ảnh sẽ tự động sắp xếp số lượng hình ảnh mới trên một vòng tròn sao cho chúng cách đều nhau và cũng điều chỉnh kích thước của vùng chứa. Bạn có thể kiểm tra điều này trong bản demo này .


Giải pháp CŨ (được bảo tồn vì lý do lịch sử)

Có, rất có thể và rất đơn giản chỉ bằng cách sử dụng CSS. Bạn chỉ cần ghi nhớ rõ ràng các góc mà bạn muốn liên kết với các hình ảnh (Tôi đã thêm một đoạn mã ở cuối chỉ để hiển thị các góc bất cứ khi nào bạn di chuột qua một trong số chúng).

Trước tiên, bạn cần một trình bao bọc. Tôi đặt đường kính của nó là 24em( width: 24em; height: 24em;không), bạn có thể đặt nó thành bất cứ thứ gì bạn muốn. Bạn cho nó position: relative;.

Sau đó, bạn đặt các liên kết của mình với các hình ảnh ở trung tâm của trình bao bọc đó, theo cả chiều ngang và chiều dọc. Bạn làm điều đó bằng cách thiết lập position: absolute;và sau đó top: 50%; left: 50%;margin: -2em;( 2embằng một nửa chiều rộng của liên kết với hình ảnh, mà tôi đã đặt là 4em- một lần nữa, bạn có thể thay đổi nó thành bất cứ điều gì bạn muốn, nhưng đừng quên thay đổi lề trong trường hợp).

Sau đó bạn quyết định về các góc độ mà bạn muốn có liên kết của bạn với những hình ảnh và bạn thêm một lớp deg{desired_angle}(ví dụ deg0hoặc deg45hoặc bất kỳ). Sau đó, đối với mỗi lớp như vậy, bạn áp dụng các biến đổi CSS theo chuỗi, như sau:

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

nơi bạn thay thế {desired_angle}bằng 0, 45v.v.

Phép biến đổi xoay thứ nhất làm quay đối tượng và các trục của nó, phép biến đổi tịnh tiến sẽ dịch đối tượng dọc theo trục X được quay và phép biến đổi xoay thứ hai đưa đối tượng trở lại vị trí.

Ưu điểm của phương pháp này là nó linh hoạt. Bạn có thể thêm hình ảnh mới ở các góc khác nhau mà không làm thay đổi cấu trúc hiện tại.

MÃ SỐ MÃ

    .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>

Ngoài ra, bạn có thể đơn giản hóa HTML hơn nữa bằng cách sử dụng hình nền cho các liên kết thay vì sử dụng imgthẻ.


CHỈNH SỬA : ví dụ với dự phòng cho IE8 trở lên (được thử nghiệm trong IE8 và IE7)


1
Đẹp, nhưng mọi người sẽ thấy gì khi truy cập từ các thiết bị / trình duyệt không hỗ trợ CSS Transform?
gkond

1
Trình duyệt máy tính để bàn duy nhất không hỗ trợ chuyển đổi CSS là IE8 trở lên. Đối với những thứ đó, điều này có thể được mô phỏng bằng cách sử dụng các phép biến đổi bộ lọc ma trận IE. Đối với trình duyệt di động, Opera Mini là trình duyệt duy nhất không hỗ trợ chuyển đổi CSS và tôi thực sự sẽ không sử dụng thứ gì đó gây lãng phí không gian trên màn hình nhỏ.
Ana

1
Khi tôi nhìn thấy bản demo, tôi đã cuộn xuống vì tôi biết chính bạn sẽ là người trả lời một câu hỏi như vậy. Làm tốt lắm @Ana. Bạn viết blog ở đâu?
Ahmad Alfy

6
@Ana thật tuyệt, đã sử dụng CSS của bạn để làm ví dụ chung cho n mục, nếu bạn quan tâm. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar

3
@Ana rất tuyệt! Bạn cảm hứng cho tôi để tạo ra một phiên bản động - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth

18

Đây là giải pháp dễ dàng mà không cần định vị tuyệt đối:

.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/


10

Dựa trên câu trả lời tuyệt vời của @ Ana, tôi đã tạo phiên bản động này cho phép bạn thêm và xóa các phần tử khỏi DOM và duy trì khoảng cách tương xứng giữa các phần tử - hãy xem tài liệu của tôi: 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>


1
Làm việc rất tốt và tôi sẽ ủng hộ nhiều hơn nếu có thể. Một vấn đề mà tôi gặp phải là nếu tôi thay đổi 360 thành bất kỳ thứ gì khác (tôi muốn một nửa hình tròn), mọi thứ sẽ không ổn. Tôi đã truy tìm nó để khai báo góc xoay và thay đổi nó thành này, var rotateAngle = zero_start + (offsetAngle * i || 0);tôi cũng đã thêm một biến cho zero_start, vì vậy nếu bạn muốn bắt đầu từ điểm 270 thay vì 0 hoặc một cái gì đó tương tự. jsfiddle.net/q59s90oy/13 . Cuối cùng, tôi đã thay đổi css cho các mục danh sách để sử dụng lợi nhuận âm. Nghiêm túc mà nói, cảm ơn vì đã chia sẻ công việc, đã giúp đỡ rất nhiều.
Joe thường xuyên

Đó là rad, rất vui vì bạn đã có thể tinh chỉnh nó khi cần thiết. Biến thể đẹp!
skwidbreth

1
Yo, đây là một hiệu ứng xoắn ốc khá hoành tráng 😅 i.imgur.com/1VrubKC.png
Ethan

@Ethan Ha ha yeah! Tôi thích làm điều đó! Tôi nghĩ nó có thể tạo ra một tác phẩm nghệ thuật tuyệt vời.
skwidbreth

5

Không có cách nào để đặt các mục có thể nhấp một cách kỳ diệu vào một vòng tròn xung quanh một phần tử khác bằng CSS. Cách tôi sẽ làm điều này là sử dụng một container với position:relative;. Và sau đó đặt tất cả các yếu tố với position:absolute;và sử dụng topleftnhắm mục tiêu vào vị trí của nó.

Mặc dù bạn chưa đặt trong các thẻ của bạn, tốt nhất nên sử dụng jQuery / javascript cho việc này.

Bước đầu tiên là đặt hình ảnh trung tâm của bạn một cách hoàn hảo vào giữa vùng chứa bằng cách sử dụng position:relative;.

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

Sau đó, bạn có thể đặt các phần tử khác xung quanh nó bằng cách sử dụng một offset()trong số Hình ảnh trung tâm trừ đi phần offset()của vùng chứa. Cung cấp cho bạn chính xác toplefthình ảnh.

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 
});

Những gì tôi đã làm ở đây là đặt các phần tử liên quan đến centerImage. Hi vọng điêu nay co ich.


5

Bạn chắc chắn có thể làm điều đó với css thuần túy hoặc sử dụng JavaScript. Đề xuất của tôi:

  • Nếu bạn đã biết rằng số lượng hình ảnh sẽ không bao giờ thay đổi, chỉ cần tính toán kiểu của bạn và đi với css đơn giản (ưu: hiệu suất tốt hơn, rất đáng tin cậy)

  • Nếu con số có thể thay đổi động trong ứng dụng của bạn hoặc chỉ có thể thay đổi trong tương lai, hãy sử dụng giải pháp Js (ưu: nhiều khả năng chống lại tương lai)

Tôi có một công việc tương tự phải làm, vì vậy tôi đã tạo một tập lệnh và mở nguồn ở đây trên Github cho bất kỳ ai có thể cần nó. Nó chỉ chấp nhận một số giá trị cấu hình và chỉ cần xuất ra mã CSS bạn cần.

Nếu bạn muốn sử dụng giải pháp Js, đây là một con trỏ đơn giản có thể hữu ích cho bạn. Sử dụng html này làm điểm bắt đầu là #boxvùng chứa và .dothình ảnh / div ở giữa bạn muốn tất cả các hình ảnh khác của mình xung quanh:

Bắt đầu html:

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

Bắt đầu 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;
}

Bạn có thể tạo một hàm nhanh theo các dòng sau:

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;
}

Bạn có thể xem một ví dụ trực tiếp tại đây


4

Sử dụng giải pháp do @Ana đề xuất:

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

Tôi đã tạo jsFiddle sau đây để đặt các vòng kết nối động bằng cách sử dụng JavaScript thuần túy (phiên bản jQuery cũng có sẵn).

Cách nó hoạt động khá đơn giản:

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>


2

Đây là một phiên bản tôi đã tạo trong React từ các ví dụ ở đây.

Ví dụ về CodeSandbox

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>
  );
}

Câu trả lời tuyệt vời và đoạn mã tuyệt vời, vấn đề duy nhất là bạn đã đăng nó trên một câu trả lời không liên quan gì đến React!
Không có thương hiệu Manchester

Tôi biết, một câu trả lời không ai yêu cầu nhưng dù sao thì nó vẫn ở đây hehe :)
br3ntor

Tôi đến đây để tìm kiếm một giải pháp mà tôi có thể sử dụng trong React để vẫn rất hữu ích
Abhishek Kasedlydy

1

Bạn có thể làm như thế này: fiddle

Đừng bận tâm đến vị trí, đó là một ví dụ nhanh

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.