113 votes

Positionner les icônes dans le cercle

Comment puis-je positionner plusieurs <img> 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 la façon d'obtenir cet effet.

Desired Result

Est-ce que c'est possible ?

223voto

Ana Points 19473

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é à l'aide de PHP, de JS, d'un préprocesseur HTML, etc... cela a moins d'importance car l'idée de base est la même.

Voici le code de Pug qui ferait cela :

//- 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é ressemble à ce qui suit (et oui, vous pouvez aussi écrire le HTML manuellement, mais il sera difficile de le modifier 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, par exemple 8em . Le site --m sont positionnés sur un cercle et c'est s'ils se trouvent au milieu des bords d'un polygone de --m des bords, qui sont tous tangents au cercle.

Si vous avez du mal à l'imaginer, vous pouvez jouer avec ceci Démonstration interactive qui construit l'arc de cercle et le cercle circonscrit pour divers polygones dont vous choisissez le nombre d'arêtes en faisant glisser le curseur.

incircle and circumcircle of a hexagon

Cela nous indique que la taille du conteneur doit être égale à 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 une propriété personnalisée). --tan ) et le bord du polygone. Nous voulons probablement que le bord du polygone ait au moins la taille des images, mais la quantité que 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 fait 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.

Ainsi, l'ajout ou la suppression d'une image dans le tableau d'images permet de disposer automatiquement le nouveau nombre d'images sur un cercle de manière à ce qu'elles soient espacées de manière égale et d'ajuster également la taille du conteneur. Vous pouvez tester ceci dans cette démo .


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

Oui, c'est tout à fait possible et très simple en utilisant simplement CSS. Il suffit d'avoir clairement en tête les angles auxquels vous voulez que les liens avec les images (j'ai ajouté un bout de code à la fin juste pour montrer les angles lorsque vous survolez l'un d'eux).

Vous avez d'abord besoin d'une enveloppe. J'ai fixé son diamètre à 24em ( width: 24em; height: 24em; fait cela), vous pouvez le définir comme vous le souhaitez. Vous lui donnez position: relative; .

Vous positionnez ensuite vos liens avec les images au centre de ce wrapper, à la fois horizontalement et verticalement. Pour ce faire, vous devez définir position: absolute; et ensuite top: 50%; left: 50%; y margin: -2em; (où 2em correspond à la moitié de la largeur du lien avec l'image, que j'ai définie comme suit 4em - Encore une fois, vous pouvez le modifier comme vous le souhaitez, mais n'oubliez pas de modifier la marge dans ce cas).

Vous décidez ensuite des angles auxquels vous voulez avoir vos liens avec les images et vous ajoutez une classe deg{desired_angle} (par exemple deg0 ou deg45 ou autre). Ensuite, pour chacune de ces classes, vous appliquez des transformations CSS en chaîne, comme ceci :

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

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

La première transformation de rotation fait pivoter l'objet et ses axes, la transformation de translation déplace l'objet le long de l'axe X pivoté et la deuxième 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 à des angles différents sans modifier la structure actuelle.

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

De plus, vous pourriez simplifier davantage le HTML en utilisant des images d'arrière-plan pour les liens au lieu d'utiliser la fonction img tags.


EDITAR : exemple avec repli pour IE8 et plus anciens (testé dans IE8 et IE7)

1 votes

C'est bien, mais qu'est-ce que les gens verront s'ils accèdent à l'information à partir d'appareils/navigateurs qui ne prennent pas en charge la transformation CSS ?

1 votes

Les seuls navigateurs de bureau qui ne prennent pas en charge les transformations CSS sont IE8 et les anciens. Pour ces derniers, il est possible d'émuler les transformations en utilisant les filtres matriciels d'IE. En ce qui concerne les navigateurs mobiles, Opera Mini est le seul à ne pas prendre en charge les transformations CSS et, de toute façon, je n'utiliserais pas quelque chose qui gaspille autant d'espace sur un petit écran.

0 votes

J'ai ajouté un exemple avec repli pour IE8 et les versions antérieures.

19voto

Itay Grudev Points 2014

En utilisant la solution proposée par @Ana :

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

J'ai créé ce qui suit jsFiddle qui place les cercles de manière dynamique en utilisant simplement JavaScript (une version jQuery est également disponible).

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

18voto

gkond Points 740

Voici la solution facile 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/

15voto

skwidbreth Points 2287

En me basant 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 proportionnel entre les éléments - regardez mon bidule : 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 votes

Ça a bien fonctionné, et j'aurais donné plus de votes positifs si j'avais pu. Un problème que j'ai rencontré est que si je changeais 360 en quelque chose d'autre (je voulais un demi-cercle), les choses se détraquaient. J'ai remonté jusqu'à la déclaration de l'angle de rotation et l'ai changé en ceci var rotateAngle = zero_start + (offsetAngle * i || 0); J'ai également ajouté une variable pour zero_start afin que vous puissiez commencer au point 270 plutôt qu'au point 0, ou quelque chose de similaire. jsfiddle.net/q59s90oy/13 . Enfin, j'ai modifié la css des éléments de la liste pour utiliser des marges négatives. Sérieusement, merci d'avoir partagé ce travail, ça m'a beaucoup aidé.

0 votes

C'est génial, content que tu aies pu le modifier comme il fallait. Belle variation !

1 votes

Yo, c'est un effet de spirale assez épique. i.imgur.com/1VrubKC.png

14voto

br3ntor Points 51

Voici une version que j'ai réalisée en 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>
  );
}

0 votes

Excellente réponse et excellent code, le seul problème est que vous l'avez posté sur une réponse qui n'a rien à voir avec React !

2 votes

Je sais, une réponse que personne n'a demandée mais la voici quand même hehe :)

3 votes

Je suis venu ici à la recherche d'une solution que je pourrais utiliser dans React, donc toujours très utile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X