Positionieren Sie die Symbole im Kreis

95

Wie kann ich mehrere <img>Elemente in einem Kreis um ein anderes positionieren und diese Elemente auch alle anklickbare Links sein? Ich möchte, dass es wie auf dem Bild unten aussieht, aber ich habe keine Ahnung, wie ich diesen Effekt erzielen kann.

Erwünschtes Ergebnis

Ist das überhaupt möglich?

FatalKeystroke
quelle

Antworten:

194

2020 Lösung

Hier ist eine modernere Lösung, die ich heutzutage verwende.

Ich beginne mit der Generierung des HTML-Codes aus einer Reihe von Bildern. Ob der HTML-Code mit PHP, JS oder einem HTML-Präprozessor generiert wird, was auch immer ... dies ist weniger wichtig, da die Grundidee dahinter dieselbe ist.

Hier ist der Mops-Code, der dies tun würde:

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

Der generierte HTML-Code sieht wie folgt aus (und ja, Sie können den HTML-Code auch manuell schreiben, aber es wird schwierig sein, danach Änderungen vorzunehmen):

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

Im CSS legen wir beispielsweise eine Größe für die Bilder fest 8em. Die --mElemente befinden sich auf einem Kreis und befinden sich in der Mitte der Kanten eines Kantenpolygons --m, die alle den Kreis tangieren.

Wenn Sie sich das nur schwer vorstellen können, können Sie mit dieser interaktiven Demo spielen, die den Kreis und den Kreis für verschiedene Polygone erstellt, deren Anzahl der Kanten Sie durch Ziehen des Schiebereglers auswählen.

Kreis und Kreis eines Sechsecks

Dies sagt uns, dass die Größe des Containers doppelt so groß sein muss wie der Radius des Kreises plus doppelt so groß wie die Größe der Bilder.

Wir kennen den Radius noch nicht, aber wir können ihn berechnen, wenn wir die Anzahl der Kanten (und damit die Tangente der Hälfte des Basiswinkels, vorberechnet und als benutzerdefinierte Eigenschaft festgelegt --tan) und die Polygonkante kennen . Wir möchten wahrscheinlich, dass die Polygonkante mindestens so groß wie die Bilder ist, aber wie viel wir an den Seiten lassen, ist willkürlich. Angenommen, wir haben auf jeder Seite die Hälfte der Bildgröße, sodass die Polygonkante doppelt so groß ist wie die Bildgröße. Dies gibt uns das folgende 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% }

In der alten Lösung finden Sie eine Erklärung der Funktionsweise der Transformationskette.

Auf diese Weise ordnet das Hinzufügen oder Entfernen eines Bilds zum Array von Bildern die neue Anzahl von Bildern auf einem Kreis automatisch so an, dass sie gleichmäßig verteilt sind, und passt auch die Größe des Containers an. Sie können dies in dieser Demo testen .


ALTE Lösung (aus historischen Gründen erhalten)

Ja, es ist sehr gut möglich und sehr einfach, nur CSS zu verwenden. Sie müssen nur die Winkel im Auge behalten, in denen Sie die Verknüpfungen mit den Bildern herstellen möchten (ich habe am Ende einen Code hinzugefügt, um die Winkel anzuzeigen, wenn Sie mit der Maus über einen von ihnen fahren).

Sie benötigen zuerst einen Wrapper. Ich stelle seinen Durchmesser so ein 24em( width: 24em; height: 24em;macht das), du kannst ihn auf alles einstellen, was du willst. Du gibst es position: relative;.

Anschließend positionieren Sie Ihre Links mit den Bildern horizontal und vertikal in der Mitte des Wrappers. Sie tun dies, indem Sie position: absolute;und dann top: 50%; left: 50%;und margin: -2em;(wo 2emist die halbe Breite des Links mit dem Bild, das ich festgelegt habe - einstellen 4em- wieder können Sie es nach Belieben ändern, aber vergessen Sie nicht, den Rand in zu ändern dieser Fall).

Sie entscheiden dann über die Winkel , in denen Sie Ihre Links mit den Bildern haben wollen und fügen Sie eine Klasse deg{desired_angle}(zum Beispiel deg0oder deg45oder was auch immer). Dann wenden Sie für jede dieser Klassen verkettete CSS-Transformationen wie folgt an:

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

wo Sie ersetzen {desired_angle}mit 0, 45und so weiter ...

Die erste Drehtransformation dreht das Objekt und seine Achsen, die Translationstransformation verschiebt das Objekt entlang der gedrehten X-Achse und die zweite Drehtransformation bringt das Objekt wieder in Position.

Der Vorteil dieser Methode ist, dass sie flexibel ist. Sie können neue Bilder in verschiedenen Winkeln hinzufügen, ohne die aktuelle Struktur zu ändern.

CODE-AUSZUG

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

Sie können den HTML-Code auch weiter vereinfachen, indem Sie Hintergrundbilder für die Links anstelle von imgTags verwenden.


BEARBEITEN : Beispiel mit Fallback für IE8 und älter (getestet in IE8 und IE7)

Ana
quelle
1
Schön, aber was werden die Leute sehen, wenn sie von Geräten / Browsern ohne CSS Transform-Unterstützung zugreifen?
Gkond
1
Die einzigen Desktop-Browser, die keine CSS-Transformationen unterstützen, sind IE8 und älter. Für diese kann dies unter Verwendung von IE-Matrixfiltertransformationen emuliert werden. Bei mobilen Browsern ist Opera Mini die einzige, die keine CSS-Transformationen unterstützt, und ich würde wirklich nichts verwenden, das auf einem kleinen Bildschirm sowieso so viel Platz verschwendet.
Ana
1
Als ich die Demo sah, scrollte ich nach unten, weil ich wusste, dass Sie eine solche Frage beantworten würden. Gut gemacht @Ana. Wo zum Teufel bloggst du?
Ahmad Alfy
6
@Ana, das ist großartig. Verwenden Sie Ihr CSS, um bei Interesse ein allgemeines Beispiel für n Elemente zu erstellen. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar
3
@Ana sehr cool! Sie haben mich dazu inspiriert, eine dynamische Version zu erstellen - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth
18

Hier ist die einfache Lösung ohne absolute Positionierung:

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

Aufbauend auf der hervorragenden Antwort von @ Ana habe ich diese dynamische Version erstellt, mit der Sie Elemente zum DOM hinzufügen und daraus entfernen und einen angemessenen Abstand zwischen den Elementen beibehalten können. Schauen Sie sich meine Geige an: 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
quelle
1
Hat super funktioniert und ich würde mehr Stimmen abgeben, wenn ich könnte. Ein Problem, das ich hatte, war, dass, wenn ich 360 auf etwas anderes umstellte (ich wollte einen Halbkreis), die Dinge aus dem Ruder liefen. Ich habe es auf die Deklaration des Drehwinkels zurückgeführt und in diese geändert. var rotateAngle = zero_start + (offsetAngle * i || 0);Außerdem habe ich eine Variable für zero_start hinzugefügt, wenn Sie also bei Punkt 270 anstatt bei 0 oder ähnlichem beginnen möchten.jsfiddle.net/q59s90oy/13 . Zuletzt habe ich das CSS für Listenelemente geändert, um negative Ränder zu verwenden. Im Ernst, danke für das Teilen der Arbeit, hat viel geholfen.
Regular Joe
Das ist großartig, ich bin froh, dass Sie es nach Bedarf anpassen konnten. Schöne Abwechslung!
Skwidbreth
1
Yo das ist ein ziemlich epischer Spiraleffekt 😅 i.imgur.com/1VrubKC.png
Ethan
@ Ethan Ha ha yeah! Ich liebe es das zu tun! Ich dachte, es könnte ein cooles Kunstwerk werden.
Skwidbreth
5

Es gibt keine Möglichkeit, anklickbare Elemente mit CSS auf magische Weise in einem Kreis um ein anderes Element zu platzieren. Die Art und Weise, wie ich dies tun würde, ist die Verwendung eines Containers mit position:relative;. Und dann platziere alle Elemente mit position:absolute;und benutze topund leftziele auf ihren Platz.

Auch wenn Sie nicht platziert haben In Ihren Tags ist es möglicherweise am besten, dafür jQuery / Javascript zu verwenden.

Der erste Schritt besteht darin, Ihr mittleres Bild mit perfekt in der Mitte des Containers zu platzieren position:relative;.

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

Danach können Sie die anderen Elemente mit einem offset()der centerImage minus der offset()des Containers um ihn herum platzieren . Geben Sie das genaue topund leftdes Bildes.

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

Was ich hier gemacht habe, ist das Platzieren der Elemente relativ zum centerImage. Hoffe das hilft.

Sem
quelle
5

Sie können dies sicherlich mit reinem CSS tun oder JavaScript verwenden. Mein Vorschlag:

  • Wenn Sie bereits wissen, dass sich die Bildnummer niemals ändern wird, berechnen Sie einfach Ihre Stile und entscheiden Sie sich für einfaches CSS (Profis: bessere Leistungen, sehr zuverlässig)

  • Wenn die Anzahl entweder dynamisch in Ihrer App oder nur in Zukunft variieren kann, wählen Sie eine Js-Lösung (Profis: zukunftssicherer)

Ich hatte einen ähnlichen Job zu erledigen, also habe ich ein Skript erstellt und es hier auf Github als Open-Source- Lösung für alle bereitgestellt , die es benötigen könnten. Es akzeptiert nur einige Konfigurationswerte und gibt einfach den benötigten CSS-Code aus.

Wenn Sie sich für die Js-Lösung entscheiden möchten, finden Sie hier einen einfachen Zeiger, der für Sie nützlich sein kann. Verwenden Sie dieses HTML als Ausgangspunkt als #boxContainer und.dot das Bild / Div in der Mitte verwenden, möchten Sie alle Ihre anderen Bilder in der Nähe haben:

HTML starten:

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

CSS starten:

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

Sie können eine Schnellfunktion in dieser Richtung erstellen:

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

Hier können Sie ein Live-Beispiel sehen

Nobita
quelle
4

Verwendung der von @Ana vorgeschlagenen Lösung:

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

Ich habe die folgende jsFiddle erstellt , die Kreise dynamisch mit einfachem JavaScript platziert (jQuery-Version ebenfalls verfügbar).

Die Art und Weise, wie es funktioniert, ist ziemlich einfach:

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

Hier ist eine Version, die ich in React aus den Beispielen hier erstellt habe.

CodeSandbox-Beispiel

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
quelle
Tolle Antwort und toller Code, das einzige Problem ist, dass Sie ihn auf eine Antwort gepostet haben, die nichts mit Reagieren zu tun hat!
Manchester ohne
Ich weiß, eine Antwort, nach der niemand gefragt hat, aber hier ist es trotzdem hehe :)
br3ntor
Ich bin hierher gekommen, um nach einer Lösung zu suchen, die ich in React verwenden kann, also immer noch sehr nützlich
Abhishek Kasireddy
1

Sie könnten es so machen: Geige

Die Positionierung macht nichts, es ist ein schnelles Beispiel

Kennzeichen
quelle