Positionner les icônes en cercle

96

Comment puis-je positionner plusieurs <img>éléments dans un cercle autour d'un autre et faire en sorte que tous ces éléments soient également des liens cliquables? Je veux que cela ressemble à l'image ci-dessous, mais je n'ai aucune idée de comment obtenir cet effet.

Résultat désiré

Est-ce seulement possible?

FatalKeystroke
la source

Réponses:

194

Solution 2020

Voici une solution plus moderne que j'utilise ces jours-ci.

Je commence par générer le HTML à partir d'un tableau d'images. Que le HTML soit généré en utilisant PHP, JS, un préprocesseur HTML, peu importe ... cela importe moins car l'idée de base derrière est la même.

Voici le code Pug qui ferait ceci:

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

Le HTML généré se présente comme suit (et oui, vous pouvez également écrire le HTML manuellement, mais il sera difficile d'apporter des modifications par la suite):

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

Dans le CSS, nous décidons d'une taille pour les images, disons 8em. Les --méléments sont positionnés sur un cercle et c'est s'ils sont au milieu des arêtes d'un polygone d' --marêtes, qui sont toutes tangentes au cercle.

Si vous avez du mal à imaginer cela, vous pouvez jouer avec cette démo interactive qui construit le cercle intérieur et le cercle circulaire pour divers polygones dont vous choisissez le nombre d'arêtes en faisant glisser le curseur.

encercle et cercle circulaire d'un hexagone

Cela nous indique que la taille du conteneur doit être deux fois le rayon du cercle plus deux fois la moitié de la taille des images.

Nous ne connaissons pas encore le rayon, mais nous pouvons le calculer si nous connaissons le nombre d'arêtes (et donc la tangente de la moitié de l'angle de base, précalculée et définie comme propriété personnalisée --tan) et l'arête du polygone. Nous voulons probablement que le bord du polygone ait au moins la taille des images, mais combien nous laissons sur les côtés est arbitraire. Disons que nous avons la moitié de la taille de l'image de chaque côté, donc le bord du polygone est deux fois la taille de l'image. Cela nous donne le CSS suivant:

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

Voir l'ancienne solution pour une explication du fonctionnement de la chaîne de transformation.

De cette façon, l'ajout ou la suppression d'une image du tableau d'images organise automatiquement le nouveau nombre d'images sur un cercle de manière à ce qu'elles soient également espacées et ajuste également la taille du conteneur. Vous pouvez tester cela dans cette démo .


ANCIENNE solution (préservée pour des raisons historiques)

Oui, c'est très possible et très simple en utilisant uniquement CSS. Vous devez juste avoir clairement à l'esprit les angles sous lesquels vous voulez les liens avec les images (j'ai ajouté un morceau de code à la fin juste pour montrer les angles chaque fois que vous survolez l'un d'eux).

Vous avez d'abord besoin d'un emballage. J'ai défini son diamètre pour être 24em( width: 24em; height: 24em;fait cela), vous pouvez le régler sur ce que vous voulez. Vous le donnez position: relative;.

Vous positionnez ensuite vos liens avec les images au centre de ce wrapper, à la fois horizontalement et verticalement. Vous faites cela en définissant position: absolute;et puis top: 50%; left: 50%;et margin: -2em;(où 2emest la moitié de la largeur du lien avec l'image, ce que j'ai défini pour être 4em- encore une fois, vous pouvez le changer comme vous le souhaitez, mais n'oubliez pas de changer la marge dans ce cas).

Vous décidez ensuite des angles sous lesquels vous souhaitez avoir vos liens avec les images et vous ajoutez une classe deg{desired_angle}(par exemple deg0ou deg45ou autre). Ensuite, pour chacune de ces classes, vous appliquez des transformations CSS chaînées, comme ceci:

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

où vous remplacez {desired_angle}avec 0, 45et ainsi de suite ...

La première transformation de rotation fait pivoter l'objet et ses axes, la transformation de translation traduit l'objet le long de l'axe X pivoté et la seconde transformation de rotation ramène l'objet en position.

L'avantage de cette méthode est qu'elle est flexible. Vous pouvez ajouter de nouvelles images sous différents angles sans modifier la structure actuelle.

EXTRAIT DE CODE

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

En outre, vous pouvez simplifier davantage le HTML en utilisant des images d'arrière-plan pour les liens au lieu d'utiliser des imgbalises.


EDIT : exemple avec fallback pour IE8 et plus ancien (testé dans IE8 et IE7)

Ana
la source
1
Bien, mais que verront les gens lorsqu'ils accèdent à partir d'appareils / navigateurs sans prise en charge de CSS Transform?
gkond
1
Les seuls navigateurs de bureau qui ne prennent pas en charge les transformations CSS sont IE8 et les versions antérieures. Pour ceux-ci, cela peut être émulé à l'aide de transformées de filtre matriciel IE. En ce qui concerne les navigateurs mobiles, Opera Mini est le seul à ne pas prendre en charge les transformations CSS et je n'utiliserais vraiment pas quelque chose qui gaspille tellement d'espace sur un petit écran de toute façon.
Ana
1
Quand j'ai vu la démo, j'ai fait défiler vers le bas car je savais que ce serait vous qui répondriez à une question comme celle-là. Bravo @Ana. Où diable bloguez-vous?
Ahmad Alfy
6
@Ana c'est génial, a utilisé votre CSS pour créer un exemple générique pour n éléments, si cela vous intéresse. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar
3
@Ana très cool! Vous m'avez inspiré pour créer une version dynamique - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth
18

Voici la solution simple sans positionnement absolu:

.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
la source
10

En m'appuyant sur l'excellente réponse de @ Ana, j'ai créé cette version dynamique qui vous permet d'ajouter et de supprimer des éléments du DOM et de maintenir un espacement proportionné entre les éléments - consultez mon violon: 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
la source
1
Cela fonctionnait très bien, et je voterais davantage si je le pouvais. Un problème que j'ai eu est que si je changeais 360 en autre chose (je voulais un demi-cercle), les choses se déréglaient. Je l'ai retracé jusqu'à la déclaration de l'angle de rotation et var rotateAngle = zero_start + (offsetAngle * i || 0);je l'ai changé pour cela, j'ai également ajouté une variable pour zero_start, donc si vous voulez commencer au point 270 plutôt qu'à 0, ou quelque chose de similaire. jsfiddle.net/q59s90oy/13 . Enfin, j'ai changé le css pour les éléments de liste pour utiliser des marges négatives. Sérieusement, merci de partager le travail, cela m'a beaucoup aidé.
Regular Joe
C'est génial, heureux que vous ayez pu l'ajuster au besoin. Belle variation!
skwidbreth
1
Yo, c'est un effet de spirale assez épique 😅 i.imgur.com/1VrubKC.png
Ethan
@Ethan Ha ha ouais! J'adore faire ça! Je pensais que cela pourrait faire une œuvre d'art cool.
skwidbreth
5

Il n'y a aucun moyen de placer par magie des éléments cliquables dans un cercle autour d'un autre élément avec CSS. La façon dont je ferais cela est d'utiliser un conteneur avec position:relative;. Et puis placez tous les éléments avec position:absolute;et en utilisant topet leftpour cibler sa place.

Même si vous n'avez pas placé dans vos balises, il peut être préférable d'utiliser jQuery / javascript pour cela.

La première étape consiste à placer parfaitement votre image centrale au centre du conteneur en utilisant position:relative;.

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

Après cela, vous pouvez placer les autres éléments autour de lui en utilisant un offset()de centerImage moins le offset()du conteneur. Vous donnant l'exact topet leftde l'image.

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

Ce que j'ai fait ici, c'est de placer les éléments par rapport à centerImage. J'espère que cela t'aides.

Sem
la source
5

Vous pouvez certainement le faire avec du CSS pur ou utiliser JavaScript. Ma suggestion:

  • Si vous savez déjà que le nombre d'images ne changera jamais, calculez simplement vos styles et optez pour le CSS (pour: meilleures performances, très fiable)

  • Si le nombre peut varier de manière dynamique dans votre application ou peut simplement varier à l'avenir, optez pour une solution Js (avantages: plus à l'épreuve du temps)

J'avais un travail similaire à faire, alors j'ai créé un script et l'ai open source ici sur Github pour tous ceux qui pourraient en avoir besoin. Il accepte simplement certaines valeurs de configuration et génère simplement le code CSS dont vous avez besoin.

Si vous souhaitez opter pour la solution Js, voici un simple pointeur qui peut vous être utile. En utilisant ce html comme point de départ étant #boxle conteneur et .dotl'image / div au milieu, vous voulez toutes vos autres images autour:

Démarrage html:

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

Démarrage de 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;
}

Vous pouvez créer une fonction rapide en suivant ces lignes:

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

Vous pouvez voir un exemple en direct ici

Nobita
la source
4

En utilisant la solution proposée par @Ana:

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

J'ai créé le jsFiddle suivant qui place les cercles de manière dynamique à l'aide de JavaScript brut (version jQuery également disponible).

La façon dont cela fonctionne est assez simple:

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
la source
2

Voici une version que j'ai créée dans React à partir des exemples ici.

Exemple de 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
la source
Excellente réponse et bon morceau de code, le seul problème est que vous l'avez posté sur une réponse qui n'a rien à voir avec React!
Unbranded Manchester le
Je sais, une réponse personne n'a demandé mais ici c'est quand même hehe :)
br3ntor
Je suis venu ici à la recherche d'une solution que je pourrais utiliser dans React donc toujours très utile
Abhishek Kasireddy
1

Vous pouvez le faire comme ceci: violon

Ne vous inquiétez pas du positionnement, c'est un exemple rapide

marque
la source