Simgeleri daireye yerleştirin


96

Birkaç <img>öğeyi diğerinin etrafındaki bir daireye nasıl yerleştirebilirim ve bu öğelerin hepsinin tıklanabilir bağlantılar olmasını nasıl sağlayabilirim ? Aşağıdaki resimdeki gibi görünmesini istiyorum, ancak bu etkiyi nasıl elde edeceğime dair hiçbir fikrim yok.

İstenen sonuç

Bu mümkün mü?

Yanıtlar:


201

2020 çözümü

İşte bugünlerde kullandığım daha modern bir çözüm.

Bir dizi görüntüden başlayarak HTML oluşturarak başlarım. HTML ister PHP, JS, ister HTML ön işlemcisi kullanılarak oluşturulmuş olsun, her neyse ... arkasındaki temel fikir aynı olduğu için bu daha az önemli.

İşte bunu yapacak Pug kodu:

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

Oluşturulan HTML aşağıdaki gibi görünür (ve evet, HTML'yi manuel olarak da yazabilirsiniz, ancak daha sonra değişiklik yapmak zor olacaktır):

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

CSS'de görseller için bir boyut belirledik diyelim 8em. --mÜrün bir daire üzerinde yer aldıkları ve bir çokgenin kenarlarının orta iseniz öyle --mdaireye teğet hepsi kenarlarına.

Bunu hayal etmekte zorlanıyorsanız , kaydırıcıyı sürükleyerek kenar sayısını seçtiğiniz çeşitli çokgenler için incircle ve çevresel çemberi oluşturan bu etkileşimli demo ile oynayabilirsiniz .

altıgenin incircle ve sirküler

Bu bize, kabın boyutunun dairenin yarıçapının iki katı artı görüntülerin yarısı kadar olması gerektiğini söyler.

Henüz yarıçapı bilmiyoruz, ancak kenarların sayısını (ve dolayısıyla, önceden hesaplanan ve özel bir özellik olarak ayarlanmış taban açısının yarısının tanjantını --tan) ve çokgen kenarını biliyorsak bunu hesaplayabiliriz . Muhtemelen poligon kenarının en azından görüntü boyutunda olmasını istiyoruz, ancak kenarlarda ne kadar bıraktığımız keyfi. Diyelim ki her bir tarafta yarı görüntü boyutumuz var, bu yüzden çokgen kenar görüntü boyutunun iki katı. Bu bize aşağıdaki CSS'yi verir:

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

Dönüşüm zincirinin nasıl çalıştığına dair bir açıklama için eski çözüme bakın.

Bu şekilde, görüntü dizisinden bir görüntü eklemek veya çıkarmak, yeni görüntü sayısını bir daire üzerinde eşit aralıklarla yerleştirilecek şekilde otomatik olarak düzenler ve ayrıca kabın boyutunu ayarlar. Bunu bu demoda test edebilirsiniz .


ESKİ çözüm (tarihsel nedenlerle korunmuştur)

Evet, sadece CSS kullanarak çok mümkün ve çok basit. Görsellerle bağlantı kurmak istediğiniz açıları aklınızda tutmanız yeterlidir (Sonuna, sadece birinin üzerine geldiğinizde açıları göstermek için bir kod parçası ekledim).

Önce bir paketleyiciye ihtiyacınız var. Çapını 24em( width: 24em; height: 24em;bunu yapar) olarak ayarlıyorum, istediğiniz gibi ayarlayabilirsiniz. Sen ver position: relative;.

Ardından, bağlantılarınızı hem yatay hem de dikey olarak bu sarmalayıcının ortasına yerleştirirsiniz. Bunu ayarlayarak yaparsınız position: absolute;ve sonra top: 50%; left: 50%;ve margin: -2em;( 2embenim ayarladığım resimle bağlantının genişliğinin yarısı nerede 4em- yine, istediğiniz gibi değiştirebilirsiniz, ancak kenar boşluğunu değiştirmeyi unutmayın. O vaka).

Daha sonra resimlerle bağlantıları istediğiniz hangi açılardan karar ve Sınıf eklemek deg{desired_angle}(örneğin deg0ya deg45ya da her neyse). Daha sonra bu tür her bir sınıf için zincirleme CSS dönüşümleri uygularsınız, örneğin:

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

nereye değiştirmek {desired_angle}ile 0, 45ve benzeri ...

İlk döndürme dönüşümü nesneyi ve eksenlerini döndürür, çeviri dönüşümü nesneyi döndürülen X ekseni boyunca çevirir ve ikinci döndürme dönüşümü nesneyi tekrar konumuna getirir.

Bu yöntemin avantajı esnek olmasıdır. Mevcut yapıyı değiştirmeden farklı açılardan yeni görüntüler ekleyebilirsiniz.

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>

Ayrıca, imgetiketler kullanmak yerine bağlantılar için arka plan resimleri kullanarak HTML'yi daha da basitleştirebilirsiniz .


DÜZENLEME : IE8 ve daha eski sürümler için yedek ile örnek (IE8 ve IE7'de test edilmiştir)


1
Güzel, ama insanlar CSS Transform desteği olmayan cihazlardan / tarayıcılardan erişirken ne görecek?
gkond

1
CSS dönüşümlerini desteklemeyen tek masaüstü tarayıcıları IE8 ve daha eski tarayıcılardır. Bunlar için bu, IE matris filtre dönüşümleri kullanılarak taklit edilebilir. Mobil tarayıcılara gelince, Opera Mini, CSS dönüşümlerini desteklemeyen tek tarayıcıdır ve yine de küçük bir ekranda bu kadar yer israf eden bir şey kullanmam.
Ana

1
Demoyu gördüğümde aşağı kaydırdım çünkü böyle bir soruyu cevaplayanın sen olacağını biliyordum. Aferin @Ana. Hangi cehennemde blog yazıyorsun?
Ahmad Alfy

6
@Ana bu harika, ilgileniyorsanız n öğe için genel bir örnek oluşturmak için CSS'nizi kullandı. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar

3
@Ana çok havalı! Dinamik bir sürüm oluşturmam için bana ilham verdin - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth

18

İşte mutlak konumlandırma olmadan kolay çözüm:

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


12

@ Ana'nın mükemmel cevabından yola çıkarak, DOM'dan öğeler eklemenize ve kaldırmanıza ve öğeler arasında orantılı boşluk bırakmanıza olanak tanıyan bu dinamik sürümü oluşturdum - kemanıma bakın: 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
Harika çalıştı ve yapabilseydim daha fazla oy verirdim. Sahip olduğum bir sorun, 360'ı başka bir şeye değiştirirsem (yarım daire istedim), işlerin çılgına dönmesiydi. Onu döndürme açısı bildirimine kadar izledim ve buna değiştirdim var rotateAngle = zero_start + (offsetAngle * i || 0);, 0 yerine 270 noktasında veya benzeri bir şeyden başlamak istiyorsanız zero_start için de bir değişken ekledim. jsfiddle.net/q59s90oy/13 . Son olarak, liste öğeleri için css'yi negatif kenar boşlukları kullanacak şekilde değiştirdim. Cidden, çalışmayı paylaştığınız için teşekkürler, çok yardımcı oldu.
Normal Joe

Bu harika, gerektiği gibi ayarlayabildiğinize sevindim. Güzel varyasyon!
skwidbreth

1
Yo, bu epik bir sarmal efekti 😅 i.imgur.com/1VrubKC.png
Ethan

@Ethan Ha ha evet! Bunu yapmayı seviyorum! Harika bir sanat eseri olabileceğini düşündüm.
skwidbreth

5

CSS ile başka bir öğenin etrafındaki bir daireye tıklanabilir öğeleri sihirli bir şekilde yerleştirmenin bir yolu yoktur. Bunu yapmamın yolu bir konteyner kullanmaktır position:relative;. Ve sonra tüm öğeleri ile position:absolute;ve kullanarak yerleştirin topve leftyerini hedefleyin.

Yerleştirmemiş olsan bile etiketlerinizde bunun için jQuery / javascript kullanmak en iyisi olabilir.

İlk adım, merkez görüntünüzü kullanarak kabın merkezine mükemmel bir şekilde yerleştirmektir position:relative;.

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

Bundan sonra offset(), centerImage eksi offset()kabın bir kullanarak diğer öğeleri etrafına yerleştirebilirsiniz . Size tam topve leftgörüntünün aynısını veriyor .

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

Burada yaptığım şey, öğeleri centerImage'a göre yerleştirmek . Bu yardımcı olur umarım.


5

Kesinlikle saf css ile yapabilir veya JavaScript kullanabilirsiniz. Benim önerim:

  • Görüntü numarasının asla değişmeyeceğini zaten biliyorsanız, sadece stillerinizi hesaplayın ve sade css ile devam edin (artılar: daha iyi performanslar, çok güvenilir)

  • Sayı, uygulamanızda dinamik olarak değişebiliyorsa veya yalnızca gelecekte değişebiliyorsa, bir Js çözümüne gidin (artılar: geleceğe daha dayanıklı)

Yapacak benzer bir işim vardı, bu yüzden bir komut dosyası oluşturdum ve ihtiyaç duyabilecek herkes için buradan Github'da açtım . Yalnızca bazı yapılandırma değerlerini kabul eder ve ihtiyacınız olan CSS kodunu çıkarır.

Js çözümüne gitmek istiyorsanız, işte size yararlı olabilecek basit bir işaretçi. Bu html'yi başlangıç ​​noktası olarak kullanarak , diğer tüm resimlerinizin etrafta olmasını #boxistediğiniz kap ve .dotortadaki image / div olarak:

Html başlatılıyor:

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

Başlangıç ​​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;
}

Şu satırlar boyunca hızlı bir işlev oluşturabilirsiniz:

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

Burada canlı bir örnek görebilirsiniz


4

@Ana tarafından önerilen çözümü kullanarak:

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

Düz JavaScript kullanarak çevreleri dinamik olarak yerleştiren aşağıdaki jsFiddle'ı oluşturdum (jQuery sürümü de mevcuttur).

Çalışma şekli oldukça basit:

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>


3

İşte buradaki örneklerden React'te yaptığım bir versiyon.

CodeSandbox Örneği

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

Harika cevap ve harika bir kod parçası, tek sorun, onu React ile hiçbir ilgisi olmayan bir cevaba göndermiş olmanızdır!
Markasız Manchester

1
Biliyorum, kimsenin istemediği bir cevap ama işte yine de hehe :)
br3ntor

1
Buraya
React'te

1

Bunu şu şekilde yapabilirsin: keman

Konumlandırmaya aldırmayın, bu hızlı bir örnek

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.