Posisikan ikon ke dalam lingkaran

96

Bagaimana cara memposisikan beberapa <img>elemen ke dalam lingkaran di sekeliling yang lain dan membuat semua elemen itu juga menjadi tautan yang dapat diklik? Saya ingin terlihat seperti gambar di bawah, tetapi saya tidak tahu bagaimana cara mencapai efek itu.

Hasil yang diinginkan

Apakah ini mungkin?

FatalKeystroke
sumber

Jawaban:

194

Solusi 2020

Inilah solusi yang lebih modern yang saya gunakan hari ini.

Saya memulai dengan membuat HTML mulai dari serangkaian gambar. Apakah HTML dibuat menggunakan PHP, JS, beberapa praprosesor HTML, apa pun ... ini kurang penting karena ide dasar di baliknya sama.

Inilah kode Pug yang akan melakukan ini:

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

HTML yang dihasilkan terlihat sebagai berikut (dan ya, Anda juga dapat menulis HTML secara manual, tetapi akan merepotkan untuk membuat perubahan setelahnya):

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

Di CSS, kami memutuskan ukuran gambar, katakanlah 8em. The --mitem diposisikan di lingkaran dan itu jika mereka berada di tengah-tengah tepi poligon dari --mtepi, yang semuanya bersinggungan dengan lingkaran.

Jika Anda kesulitan membayangkannya, Anda dapat bermain dengan demo interaktif ini yang membangun incircle dan circumcircle untuk berbagai poligon yang jumlah tepinya Anda pilih dengan menyeret slider.

incircle and circumcircle dari segi enam

Ini memberi tahu kita bahwa ukuran wadah harus dua kali jari-jari lingkaran ditambah dua kali setengah ukuran gambar.

Kita belum mengetahui radiusnya, tetapi kita dapat menghitungnya jika kita mengetahui jumlah sisi (dan karenanya tangen dari setengah sudut alas, dihitung sebelumnya dan ditetapkan sebagai properti khusus --tan) dan tepi poligon. Kita mungkin ingin tepi poligon berukuran paling kecil dari gambarnya, tetapi seberapa banyak yang kita sisakan di sisi itu sewenang-wenang. Katakanlah kita memiliki setengah ukuran gambar di setiap sisi, jadi tepi poligon dua kali ukuran gambar. Ini memberi kita CSS berikut:

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

Lihat solusi lama untuk penjelasan tentang cara kerja rantai transformasi.

Dengan cara ini, menambahkan atau menghapus gambar dari larik gambar secara otomatis mengatur jumlah gambar baru pada lingkaran sedemikian rupa sehingga jaraknya sama dan juga menyesuaikan ukuran wadah. Anda bisa mengujinya di demo ini .


Solusi LAMA (dipertahankan karena alasan historis)

Ya, itu sangat mungkin dan sangat sederhana hanya dengan menggunakan CSS. Anda hanya perlu mengingat dengan jelas sudut di mana Anda menginginkan tautan dengan gambar (saya telah menambahkan sepotong kode di bagian akhir hanya untuk menunjukkan sudut setiap kali Anda mengarahkan kursor ke salah satunya).

Pertama-tama Anda membutuhkan pembungkus. Saya mengatur diameternya menjadi 24em( width: 24em; height: 24em;melakukan itu), Anda dapat mengaturnya sesuai keinginan Anda. Anda memberikannya position: relative;.

Anda kemudian memposisikan tautan Anda dengan gambar di tengah pembungkus itu, baik secara horizontal maupun vertikal. Anda melakukannya dengan mengatur position: absolute;lalu top: 50%; left: 50%;dan margin: -2em;(di mana 2emsetengah lebar tautan dengan gambar, yang telah saya tetapkan 4em- sekali lagi, Anda dapat mengubahnya menjadi apa pun yang Anda inginkan, tetapi jangan lupa untuk mengubah margin di kasus itu).

Anda kemudian memutuskan sudut di mana Anda ingin memiliki tautan Anda dengan gambar dan Anda menambahkan kelas deg{desired_angle}(misalnya deg0atau deg45atau apa pun). Kemudian untuk setiap kelas tersebut Anda menerapkan transformasi CSS berantai, seperti ini:

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

di mana Anda mengganti {desired_angle}dengan 0, 45, dan seterusnya ...

Transformasi putar pertama memutar objek dan sumbunya, transformasi terjemahan menerjemahkan objek sepanjang sumbu X yang diputar dan transformasi rotasi kedua mengembalikan objek ke posisinya.

Keuntungan dari metode ini adalah fleksibel. Anda dapat menambahkan gambar baru pada sudut yang berbeda tanpa mengubah struktur saat ini.

CUPLIKAN KODE

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

Selain itu, Anda dapat lebih menyederhanakan HTML dengan menggunakan gambar latar belakang untuk tautan daripada menggunakan imgtag.


EDIT : contoh dengan fallback untuk IE8 dan yang lebih lama (diuji di IE8 dan IE7)

Ana
sumber
1
Bagus, tapi apa yang akan dilihat orang saat mengakses dari perangkat / browser tanpa dukungan Transformasi CSS?
gkond
1
Satu-satunya browser desktop yang tidak mendukung transformasi CSS adalah IE8 dan yang lebih lama. Untuk itu, ini dapat diemulasikan menggunakan transformasi filter matriks IE. Sedangkan untuk browser seluler, Opera Mini adalah satu-satunya yang tidak mendukung transformasi CSS dan saya benar-benar tidak akan menggunakan sesuatu yang membuang-buang ruang di layar kecil.
Ana
1
Ketika saya melihat demo, saya menggulir ke bawah karena saya tahu Anda yang menjawab pertanyaan seperti itu. Kerja bagus @Ana. Dimana sih kamu ngeblog?
Ahmad Alfy
6
@Ana itu luar biasa, menggunakan CSS Anda untuk membuat contoh umum untuk n item, jika tertarik. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar
3
@Ana sangat keren! Anda menginspirasi saya untuk membuat versi dinamis - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth
18

Inilah solusi mudah tanpa pemosisian absolut:

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

Berdasarkan jawaban yang sangat baik dari @ Ana, saya membuat versi dinamis ini yang memungkinkan Anda untuk menambah dan menghapus elemen dari DOM dan mempertahankan jarak yang proporsional antara elemen - lihat biola saya: 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.dll
sumber
1
Bekerja dengan baik, dan saya akan memberi suara lebih jika saya bisa. Satu masalah yang saya hadapi adalah jika saya mengubah 360 ke hal lain (saya ingin setengah lingkaran), semuanya menjadi rusak. Saya menelusuri ke deklarasi sudut putar dan mengubahnya menjadi ini, var rotateAngle = zero_start + (offsetAngle * i || 0);saya juga menambahkan variabel untuk zero_start jadi jika Anda ingin memulai pada titik 270 daripada 0, atau yang serupa. jsfiddle.net/q59s90oy/13 . Terakhir saya mengubah css untuk item daftar menggunakan margin negatif. Serius, terima kasih telah berbagi pekerjaan, banyak membantu.
Joe Reguler
Itu bagus, senang Anda bisa mengubahnya sesuai kebutuhan. Variasi yang bagus!
skwidbreth
1
Yo, ini adalah efek spiral yang cukup epik 😅 i.imgur.com/1VrubKC.png
Ethan
@Ethan Ha ha ya! Saya suka melakukan itu! Saya pikir itu bisa membuat karya seni yang keren.
skwidbreth
5

Tidak ada cara untuk secara ajaib menempatkan item yang dapat diklik dalam lingkaran di sekitar elemen lain dengan CSS. Cara saya melakukan ini adalah dengan menggunakan wadah dengan position:relative;. Dan kemudian tempatkan semua elemen dengan position:absolute;dan menggunakan topdan leftuntuk menargetkan tempatnya.

Meskipun Anda belum menempatkan di tag Anda mungkin lebih baik menggunakan jQuery / javascript untuk ini.

Langkah pertama adalah menempatkan gambar tengah Anda dengan sempurna di tengah wadah menggunakan position:relative;.

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

Setelah itu Anda bisa menempatkan elemen lain di sekitarnya dengan menggunakan offset()centerImage dikurangi offset()container. Memberi Anda gambaran yang tepat topdan tepat left.

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

Apa yang saya lakukan di sini adalah menempatkan elemen relatif terhadap centerImage. Semoga ini membantu.

Sem
sumber
5

Anda pasti bisa melakukannya dengan css murni atau menggunakan JavaScript. Saran saya:

  • Jika Anda sudah tahu bahwa jumlah gambar tidak akan pernah berubah, hitung saja gaya Anda dan gunakan css biasa (pro: kinerja lebih baik, sangat andal)

  • Jika jumlahnya dapat bervariasi baik secara dinamis di aplikasi Anda atau hanya dapat bervariasi di masa mendatang, gunakan solusi Js (pro: lebih tahan masa depan)

Saya memiliki pekerjaan serupa untuk dilakukan, jadi saya membuat skrip dan open source di sini di Github untuk siapa saja yang mungkin membutuhkannya. Itu hanya menerima beberapa nilai konfigurasi dan hanya mengeluarkan kode CSS yang Anda butuhkan.

Jika Anda ingin mencari solusi Js, berikut adalah penunjuk sederhana yang dapat berguna bagi Anda. Menggunakan html ini sebagai titik awal sebagai #boxwadah dan .dotgambar / div di tengah Anda ingin semua gambar Anda yang lain ada:

Memulai html:

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

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

Anda dapat membuat fungsi cepat di sepanjang baris ini:

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

Anda dapat melihat contoh langsung di sini

Nobita
sumber
4

Menggunakan solusi yang diusulkan oleh @Ana:

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

Saya membuat jsFiddle berikut yang menempatkan lingkaran secara dinamis menggunakan JavaScript biasa (versi jQuery juga tersedia).

Cara kerjanya cukup sederhana:

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

Ini adalah versi yang saya buat di React dari contoh di sini.

Contoh 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.dll
sumber
Jawaban yang bagus dan potongan kode yang bagus, satu-satunya masalah adalah Anda telah mempostingnya pada jawaban yang tidak ada hubungannya dengan React!
Manchester tidak bermerek
Saya tahu, jawaban yang tidak diminta siapa pun tetapi ini dia sih hehe :)
br3ntor
Saya datang ke sini mencari solusi yang dapat saya gunakan di React sehingga masih sangat berguna
Abhishek Kasireddy
1

Anda bisa melakukannya seperti ini: biola

Jangan pedulikan pemosisiannya, ini contoh cepat

Menandai
sumber