Umieść ikony w okręgu

96

Jak ustawić kilka <img>elementów w kółko wokół innego i sprawić, by wszystkie te elementy były również klikalnymi linkami? Chcę, żeby wyglądało jak na poniższym obrazku, ale nie mam pojęcia, jak to osiągnąć.

Pożądany rezultat

Czy to w ogóle możliwe?

FatalKeystroke
źródło

Odpowiedzi:

197

Rozwiązanie 2020

Oto bardziej nowoczesne rozwiązanie, z którego korzystam obecnie.

Zaczynam od wygenerowania kodu HTML, zaczynając od tablicy obrazów. Niezależnie od tego, czy HTML jest generowany za pomocą PHP, JS, jakiegoś preprocesora HTML, cokolwiek ... ma to mniejsze znaczenie, ponieważ podstawowa idea jest taka sama.

Oto kod mopsa, który mógłby to zrobić:

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

Wygenerowany kod HTML wygląda następująco (i tak, możesz też napisać kod HTML ręcznie, ale później wprowadzanie zmian będzie trudne):

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

Powiedzmy, że w CSS decydujemy o rozmiarze obrazów 8em. Te --melementy są umieszczone na kole i to, czy są one w środku krawędzi wielokąta z --mkrawędziami, z których wszystkie są styczne do okręgu.

Jeśli trudno ci to sobie wyobrazić, możesz pobawić się tym interaktywnym demo, które tworzy okrąg i okrąg dla różnych wielokątów, których liczbę krawędzi wybierasz, przeciągając suwak.

incircle i circumcircle z sześciokąta

To mówi nam, że rozmiar pojemnika musi być dwa razy większy od promienia okręgu plus dwa razy mniejszy od rozmiaru obrazów.

Nie znamy jeszcze promienia, ale możemy go obliczyć, jeśli znamy liczbę krawędzi (a zatem styczną połowy kąta bazowego, wstępnie obliczoną i ustawioną jako właściwość niestandardowa --tan) i krawędź wielokąta. Prawdopodobnie chcemy, aby krawędź wielokąta była co najmniej wielkości obrazów, ale to, ile pozostawiamy po bokach, jest arbitralne. Powiedzmy, że mamy połowę rozmiaru obrazu z każdej strony, więc krawędź wielokąta jest dwukrotnie większa od rozmiaru obrazu. To daje nam następujący 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% }

Zobacz stare rozwiązanie, aby wyjaśnić, jak działa łańcuch transformacji.

W ten sposób dodanie lub usunięcie obrazu z tablicy obrazów automatycznie rozmieszcza nową liczbę obrazów w okręgu, tak aby były równo rozmieszczone, a także dostosowuje rozmiar kontenera. Możesz to sprawdzić w tym demo .


STARE rozwiązanie (zachowane ze względów historycznych)

Tak, jest to bardzo możliwe i bardzo proste przy użyciu samego CSS. Musisz tylko mieć na uwadze kąty, pod którymi chcesz tworzyć linki z obrazami (dodałem na końcu fragment kodu, aby pokazać kąty za każdym razem, gdy najedziesz na jeden z nich).

Najpierw potrzebujesz opakowania. Ustawiłem jego średnicę na 24em( width: 24em; height: 24em;robi to), możesz ustawić ją na dowolną. Ty to dajesz position: relative;.

Następnie umieść linki z obrazami w środku tego opakowania, zarówno w poziomie, jak i w pionie. Robisz to, ustawiając, position: absolute;a następnie top: 50%; left: 50%;i margin: -2em;(gdzie 2emjest połowa szerokości linku z obrazem, na który ustawiłem 4em- znowu możesz to zmienić na cokolwiek chcesz, ale nie zapomnij zmienić marginesu w ta walizka).

Następnie decyduje o prostym, w którym chcesz mieć swoje powiązania z obrazami i dodać klasę deg{desired_angle}(na przykład deg0lub deg45lub cokolwiek). Następnie dla każdej takiej klasy zastosujesz łańcuchowe transformacje CSS, na przykład:

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

gdzie można wymienić {desired_angle}się 0, 45i tak dalej ...

Pierwsza transformacja z obrotem obraca obiekt i jego osie, transformacja z obrotem powoduje przesunięcie obiektu wzdłuż obróconej osi X, a druga transformacja z obrotem przywraca obiekt na miejsce.

Zaletą tej metody jest to, że jest elastyczna. Możesz dodawać nowe obrazy pod różnymi kątami bez zmiany aktualnej struktury.

KOD SNIPPET

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

Możesz także dodatkowo uprościć kod HTML, używając obrazów tła dla łączy zamiast imgtagów.


EDYCJA : przykład z rezerwą dla IE8 i starszych (testowane w IE8 i IE7)

Ana
źródło
1
Fajnie, ale co zobaczą ludzie podczas uzyskiwania dostępu z urządzeń / przeglądarek bez obsługi transformacji CSS?
gkond
1
Jedynymi przeglądarkami komputerowymi, które nie obsługują przekształceń CSS, są IE8 i starsze. W tym przypadku można to emulować za pomocą transformacji filtra macierzy IE. Jeśli chodzi o przeglądarki mobilne, Opera Mini jako jedyna nie obsługuje transformacji CSS i naprawdę nie użyłbym czegoś, co marnuje tak dużo miejsca na małym ekranie.
Ana
1
Kiedy zobaczyłem demo, przewinąłem w dół, ponieważ wiedziałem, że to ty odpowiesz na takie pytanie. Dobra robota @Ana. Gdzie, do cholery, blogujesz?
Ahmad Alfy
6
@Ana to niesamowite, użyłem twojego CSS do stworzenia ogólnego przykładu dla n elementów, jeśli jesteś zainteresowany. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar
3
@Ana bardzo fajna! Zainspirowałeś mnie do stworzenia wersji dynamicznej - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth
18

Oto proste rozwiązanie bez pozycjonowania bezwzględnego:

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

gkond
źródło
12

Opierając się na doskonałej odpowiedzi @ Any, stworzyłem tę dynamiczną wersję, która pozwala dodawać i usuwać elementy z DOM oraz zachować proporcjonalne odstępy między elementami - sprawdź moje skrzypce: 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>

skwidbreth
źródło
1
Działało świetnie i zagłosowałbym więcej, gdybym mógł. Jedynym problemem, jaki miałem, było to, że gdy zmieniłem 360 na cokolwiek innego (chciałem półkola), sprawy się nie powiodły. Prześledziłem to do deklaracji kąta obrotu i zmieniłem na to var rotateAngle = zero_start + (offsetAngle * i || 0);Dodałem również zmienną dla zero_start, więc jeśli chcesz zacząć od punktu 270, a nie 0, lub coś podobnego. jsfiddle.net/q59s90oy/13 . Na koniec zmieniłem css dla elementów listy, aby używały ujemnych marginesów. Poważnie jednak, dzięki za podzielenie się pracą, bardzo pomogło.
Zwykły Joe
To super, cieszę się, że udało ci się to dostosować w razie potrzeby. Niezła odmiana!
skwidbreth
1
Hej, to dość epicki efekt spirali 😅 i.imgur.com/1VrubKC.png
Ethan,
@Ethan Ha ha yeah! Uwielbiam to robić! Pomyślałem, że może to być fajne dzieło sztuki.
skwidbreth
5

Nie ma możliwości magicznego umieszczenia klikalnych elementów w okręgu wokół innego elementu za pomocą CSS. Sposób, w jaki bym to zrobił, to użycie kontenera z position:relative;. A następnie umieść wszystkie elementy za position:absolute;pomocą topi używając i leftaby wycelować w to miejsce.

Nawet jeśli nie umieściłeś w swoich tagach najlepiej do tego celu użyć jQuery / javascript.

Pierwszym krokiem jest idealne umieszczenie centralnego obrazu na środku pojemnika za pomocą position:relative;.

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

Następnie możesz umieścić inne elementy wokół niego, używając offset()of the centerImage minus offset()kontener. Co daje dokładny topi leftobrazu.

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

To, co tutaj zrobiłem, to umieszczenie elementów względem centerImage. Mam nadzieję że to pomoże.

Sem
źródło
5

Z pewnością możesz to zrobić za pomocą czystego CSS lub użyć JavaScript. Moja sugestia:

  • Jeśli już wiesz, że liczba zdjęć nigdy się nie zmieni, po prostu oblicz swoje style i korzystaj z prostego CSS (plusy: lepsza wydajność, bardzo niezawodna)

  • Jeśli liczba ta może się zmieniać dynamicznie w aplikacji lub może się zmieniać w przyszłości, wybierz rozwiązanie Js (zalety: bardziej przyszłościowe)

Miałem podobną pracę do wykonania, więc stworzyłem skrypt i otworzyłem go tutaj na Github dla każdego, kto mógł go potrzebować. Po prostu akceptuje niektóre wartości konfiguracyjne i po prostu wyświetla kod CSS, którego potrzebujesz.

Jeśli chcesz skorzystać z rozwiązania Js, oto prosty wskaźnik, który może Ci się przydać. Używając tego html jako punktu wyjścia, będąc #boxkontenerem i .dotobrazem / div w środku, wokół którego chcesz umieścić wszystkie inne obrazy:

Uruchamianie html:

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

Początek 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;
}

Możesz utworzyć szybką funkcję w następujący sposób:

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

Można zobaczyć na żywo przykład tutaj

Aurelio
źródło
4

Korzystając z rozwiązania zaproponowanego przez @Ana:

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

Stworzyłem następujący jsFiddle, który dynamicznie umieszcza okręgi przy użyciu zwykłego JavaScript (dostępna jest również wersja jQuery).

Sposób działania jest dość prosty:

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>

Itay Grudev
źródło
3

Oto wersja, którą stworzyłem w React na podstawie przykładów tutaj.

Przykład 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>
  );
}
br3ntor
źródło
Świetna odpowiedź i świetny fragment kodu, jedyny problem polega na tym, że opublikowałeś go w odpowiedzi, która nie ma nic wspólnego z React!
Niemarkowy Manchester
1
Wiem, odpowiedź, o którą nikt nie pytał, ale i tak jest hehe :)
br3ntor
Przyszedłem tutaj, szukając rozwiązania, które mógłbym wykorzystać w Reakcie, tak nadal bardzo przydatnego
Abhishek Kasireddy
1

Możesz to zrobić tak: skrzypce

Nie przejmuj się pozycjonowaniem, to szybki przykład

znak
źródło