Posiziona le icone nel cerchio


96

Come posso posizionare più <img>elementi in un cerchio attorno a un altro e fare in modo che anche questi elementi siano link cliccabili? Voglio che assomigli all'immagine qui sotto, ma non ho idea di come ottenere quell'effetto.

Risultato desiderato

È anche possibile?

Risposte:


194

Soluzione 2020

Ecco una soluzione più moderna che uso in questi giorni.

Inizio generando l'HTML partendo da un array di immagini. Se l'HTML viene generato usando PHP, JS, qualche preprocessore HTML, qualunque cosa ... questo conta meno perché l'idea di base è la stessa.

Ecco il codice Pug che lo farebbe:

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

L'HTML generato appare come segue (e sì, puoi anche scrivere l'HTML manualmente, ma sarà difficile apportare modifiche in seguito):

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

Nel CSS, decidiamo una dimensione per le immagini, diciamo 8em. Gli --melementi sono posizionati su un cerchio ed è se sono al centro dei bordi di un poligono di --mbordi, tutti tangenti al cerchio.

Se hai difficoltà a immaginarlo, puoi giocare con questa demo interattiva che costruisce l'incircle e circumcircle per vari poligoni di cui scegli il numero di bordi trascinando il cursore.

incircle e circumcircle di un esagono

Questo ci dice che la dimensione del contenitore deve essere il doppio del raggio del cerchio più due volte la metà delle dimensioni delle immagini.

Non conosciamo ancora il raggio, ma possiamo calcolarlo se conosciamo il numero di bordi (e quindi la tangente della metà dell'angolo di base, precalcolata e impostata come proprietà personalizzata --tan) e il bordo del poligono. Probabilmente vogliamo che il bordo del poligono abbia almeno la dimensione delle immagini, ma quanto lasciamo sui lati è arbitrario. Supponiamo di avere la metà delle dimensioni dell'immagine su ciascun lato, quindi il bordo del poligono è il doppio della dimensione dell'immagine. Questo ci dà il seguente 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% }

Vedere la vecchia soluzione per una spiegazione di come funziona la catena di trasformazione.

In questo modo, l'aggiunta o la rimozione di un'immagine dall'array di immagini dispone automaticamente il nuovo numero di immagini su un cerchio in modo che siano equidistanti e regola anche la dimensione del contenitore. Puoi provarlo in questa demo .


VECCHIA soluzione (conservata per ragioni storiche)

Sì, è molto possibile e molto semplice usando solo i CSS. Hai solo bisogno di avere ben in mente gli angoli a cui vuoi i collegamenti con le immagini (ho aggiunto un pezzo di codice alla fine solo per mostrare gli angoli ogni volta che passi il mouse su uno di essi).

Prima hai bisogno di un involucro. Ho impostato il suo diametro su 24em( width: 24em; height: 24em;fa quello), puoi impostarlo su quello che vuoi. Lo dai position: relative;.

Quindi posiziona i tuoi collegamenti con le immagini al centro di quell'involucro, sia orizzontalmente che verticalmente. Puoi farlo impostando position: absolute;e quindi top: 50%; left: 50%;e margin: -2em;(dove 2emè metà della larghezza del collegamento con l'immagine, che ho impostato per essere 4em- di nuovo, puoi cambiarlo come desideri, ma non dimenticare di cambiare il margine in questo caso).

È quindi decidere gli angoli in cui si vogliono avere i tuoi link con le immagini e si aggiunge una classe deg{desired_angle}(ad esempio deg0o deg45o qualsiasi altra cosa). Quindi per ciascuna di queste classi si applicano trasformazioni CSS concatenate, in questo modo:

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

dove si sostituisce {desired_angle}con 0, 45e così via ...

La prima trasformazione di rotazione ruota l'oggetto e i suoi assi, la trasformazione di traslazione trasla l'oggetto lungo l'asse X ruotato e la seconda trasformazione di rotazione riporta l'oggetto in posizione.

Il vantaggio di questo metodo è che è flessibile. È possibile aggiungere nuove immagini da diverse angolazioni senza alterare la struttura corrente.

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

Inoltre, potresti semplificare ulteriormente l'HTML utilizzando immagini di sfondo per i collegamenti invece di utilizzare i imgtag.


EDIT : esempio con fallback per IE8 e versioni precedenti (testato in IE8 e IE7)


1
Bello, ma cosa vedranno le persone quando accedono da dispositivi / browser senza supporto CSS Transform?
gkond

1
Gli unici browser desktop che non supportano le trasformazioni CSS sono IE8 e precedenti. Per quelli, questo può essere emulato usando le trasformazioni del filtro a matrice di IE. Per quanto riguarda i browser mobili, Opera Mini è l'unico che non supporta le trasformazioni CSS e comunque non userei qualcosa che spreca così tanto spazio su un piccolo schermo.
Ana

1
Quando ho visto la demo ho fatto scorrere verso il basso perché sapevo che saresti stato tu a rispondere a una domanda del genere. Ben fatto @Ana. Dove diavolo scrivi?
Ahmad Alfy

6
@Ana è fantastico, ha usato il tuo CSS per fare un esempio generico per n elementi, se interessati. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar

3
@Ana very cool! Mi hai ispirato a creare una versione dinamica - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth

18

Ecco la soluzione facile senza posizionamento assoluto:

.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

Basandomi sull'eccellente risposta di @ Ana, ho creato questa versione dinamica che ti consente di aggiungere e rimuovere elementi dal DOM e mantenere una spaziatura proporzionata tra gli elementi: controlla il mio violino: 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
Ha funzionato alla grande e avrei votato di più se potessi. Un problema che ho avuto è che se ho cambiato 360 in qualcos'altro (volevo un semicerchio), le cose sono andate fuori di testa. L'ho tracciato nella dichiarazione dell'angolo di rotazione e l' var rotateAngle = zero_start + (offsetAngle * i || 0);ho cambiato in questo ho anche aggiunto una variabile per zero_start quindi se vuoi iniziare dal punto 270 anziché 0, o qualcosa di simile. jsfiddle.net/q59s90oy/13 . Infine ho cambiato il css per gli elementi dell'elenco per utilizzare i margini negativi. Seriamente, però, grazie per aver condiviso il lavoro, mi ha aiutato molto.
Regular Joe

È fantastico, sono contento che tu sia stato in grado di modificarlo secondo necessità. Bella variazione!
skwidbreth

1
Yo questo è un effetto spirale piuttosto epico 😅 i.imgur.com/1VrubKC.png
Ethan

@ Ethan Ha ah yeah! Mi piace farlo! Ho pensato che potesse fare un bel pezzo d'arte.
skwidbreth

5

Non c'è modo di posizionare magicamente elementi cliccabili in un cerchio attorno a un altro elemento con CSS. Il modo in cui lo farei è usare un contenitore con position:relative;. E poi posiziona tutti gli elementi con position:absolute;e usando tope leftper mirare al suo posto.

Anche se non hai posizionato nei tuoi tag potrebbe essere meglio usare jQuery / javascript per questo.

Il primo passo è posizionare perfettamente l'immagine centrale al centro del contenitore usando position:relative;.

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

Dopodiché puoi posizionare gli altri elementi attorno ad esso usando una offset()delle centerImage meno la offset()del contenitore. Dandoti l'esatto tope leftdell'immagine.

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

Quello che ho fatto qui è posizionare gli elementi rispetto a centerImage. Spero che questo ti aiuti.


5

Puoi certamente farlo con puro css o usare JavaScript. Il mio consiglio:

  • Se sai già che il numero delle immagini non cambierà mai, calcola i tuoi stili e vai con un semplice css (pro: prestazioni migliori, molto affidabile)

  • Se il numero può variare dinamicamente nella tua app o semplicemente può variare in futuro, scegli una soluzione Js (vantaggi: più a prova di futuro)

Avevo un lavoro simile da fare, quindi ho creato uno script e l'ho reso open source qui su Github per chiunque ne avesse bisogno. Accetta solo alcuni valori di configurazione e restituisce semplicemente il codice CSS necessario.

Se vuoi optare per la soluzione Js, ecco un semplice puntatore che può esserti utile. Usando questo html come punto di partenza essendo #boxil contenitore e .dotl'immagine / div nel mezzo, vuoi che tutte le altre immagini siano intorno:

Avvio di HTML:

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

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

Puoi creare una funzione rapida seguendo queste linee:

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

Puoi vedere un esempio dal vivo qui


4

Utilizzando la soluzione proposta da @Ana:

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

Ho creato il seguente jsFiddle che posiziona i cerchi dinamicamente utilizzando JavaScript semplice (disponibile anche la versione jQuery).

Il modo in cui funziona è piuttosto semplice:

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

Ecco una versione che ho realizzato in React dagli esempi qui.

CodeSandbox Esempio

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

Ottima risposta e ottimo pezzo di codice, l'unico problema è che l'hai pubblicato su una risposta che non ha nulla a che fare con React!
Manchester senza marchio il

Lo so, una risposta che nessuno ha chiesto ma eccola comunque hehe :)
br3ntor

Sono venuto qui alla ricerca di una soluzione che potessi usare in React, quindi ancora molto utile
Abhishek Kasireddy,

1

Potresti farlo in questo modo: violino

Non importa il posizionamento, è un rapido esempio

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.