Розташуйте піктограми у колі


96

Як я можу розташувати кілька <img>елементів у колі навколо іншого, і щоб усі ці елементи також були посиланнями, які можна натиснути? Я хочу, щоб це було схоже на малюнок нижче, але я не уявляю, як досягти цього ефекту.

Бажаний результат

Чи можливо це взагалі?

Відповіді:


194

Рішення 2020 року

Ось більш сучасне рішення, яке я використовую сьогодні.

Я починаю з генерації HTML, починаючи з масиву зображень. Чи генерується HTML за допомогою PHP, JS, якогось препроцесора HTML, що завгодно ... це має менше значення, оскільки основна ідея однакова.

Ось код мопса, який міг би це зробити:

//- 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 виглядає наступним чином (і так, ви також можете написати HTML вручну, але після цього буде важко вносити зміни):

<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 ми визначаємо розмір зображень 8em. Ці --mелементи розташовані по колу , і це , якщо вони знаходяться в середині ребер багатокутника --mкраю, всі з яких є дотичною до кола.

Якщо вам складно це уявити, ви можете пограти з цією інтерактивною демонстрацією, яка створює кругообіг та описану коло для різних полігонів, кількість ребер яких ви вибираєте, перетягуючи повзунок.

вкладене та введене коло шестикутника

Це говорить нам, що розмір контейнера повинен бути вдвічі більшим за радіус кола плюс удвічі меншим за половину розміру зображень.

Ми ще не знаємо радіуса, але ми можемо обчислити його, якщо знаємо кількість ребер (і, отже, тангенс половини базового кута, попередньо обчислений і встановлений як спеціальна властивість --tan) і ребро багатокутника. Ми, напевно, хочемо, щоб край багатокутника був мінімальним за розміром зображень, але скільки ми залишаємо з боків довільно. Скажімо, ми маємо половину розміру зображення на кожній стороні, тому край багатокутника вдвічі більший за розмір зображення. Це дає нам такий 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% }

Див. Старе рішення для пояснення того, як працює ланцюг перетворень.

Таким чином, додавання або видалення зображення з масиву зображень автоматично впорядковує нову кількість зображень по колу таким чином, щоб вони мали однаковий інтервал, а також регулює розмір контейнера. Ви можете перевірити це в цій демонстрації .


СТАРЕ рішення (збережене з історичних причин)

Так, це дуже можливо і дуже просто, використовуючи лише CSS. Вам просто потрібно мати на увазі кути, під якими ви хочете посилання із зображеннями (я додав шматок коду в кінці лише для відображення кутів, коли ви наводите один із них).

Спочатку потрібно обгортка. Я встановив його діаметр 24em( width: 24em; height: 24em;робить це), ви можете встановити його як завгодно. Ви даєте це position: relative;.

Потім ви розміщуєте свої посилання із зображеннями в центрі обгортки, як горизонтально, так і вертикально. Ви робите це, встановивши, position: absolute;а потім top: 50%; left: 50%;і margin: -2em;(де 2emполовина ширини посилання із зображенням, яке я встановив 4em- знову ж, ви можете змінити його на будь-що, що забажаєте, але не забудьте змінити поле в той випадок).

Потім Ви вибираєте кутів , при яких ви хочете , щоб ваші посилання з зображеннями і додати клас deg{desired_angle}(наприклад , deg0чи deg45або будь-який інший ). Потім для кожного такого класу ви застосовуєте ланцюгові перетворення CSS, наприклад:

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

де ви замінюєте {desired_angle}з 0, 45і так далі ...

Перше поворотне перетворення обертає об'єкт та його осі, перетворювальне перетворення переводить об'єкт уздовж оберненої осі X, а друге поворотне перетворення повертає об'єкт у своє положення.

Перевагою цього методу є його гнучкість. Ви можете додавати нові зображення під різними кутами, не змінюючи поточну структуру.

КОД СНИППЕТ

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

Крім того, ви можете додатково спростити HTML, використовуючи фонові зображення для посилань замість imgтегів.


РЕДАКТУВАТИ : приклад із запасним варіантом для IE8 і старіших (перевірено в IE8 та IE7)


1
Приємно, але що побачать люди при доступі з пристроїв / браузерів без підтримки CSS Transform?
gkond

1
Єдиними браузерами для настільних комп'ютерів, які не підтримують перетворення CSS, є IE8 і старіші. Для них це можна емулювати, використовуючи перетворення матричного фільтра IE. Що стосується мобільних браузерів, Opera Mini єдиний, хто не підтримує перетворення CSS, і я б і справді не використовував щось, що так мало витрачається на маленькому екрані.
Ана

1
Побачивши демонстраційне зображення, я прокрутив сторінку вниз, бо знав, що це ти відповісиш на таке питання. Молодці @Ana. Де, блін, ти ведеш блог?
Ахмад Алфі

6
@Ana, яка чудова, використовувала ваш CSS, щоб зробити загальний приклад для n елементів, якщо зацікавлена. jsfiddle.net/sajjansarkar/zgcgq8cg
Sajjan Sarkar

3
@Ana дуже круто! Ви надихнули мене на створення динамічної версії - jsfiddle.net/skwidbreth/q59s90oy
skwidbreth

18

Ось просте рішення без абсолютного позиціонування:

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


10

Спираючись на чудову відповідь @ Ana, я створив цю динамічну версію, яка дозволяє додавати та видаляти елементи з DOM та підтримувати пропорційний інтервал між елементами - перегляньте мою скрипту: 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
Працював чудово, і я б підтримав більше, якби міг. У мене була одна проблема: якщо я змінив 360 на що-небудь інше (я хотів півкола), все вийшло з ладу. Я простежив його до оголошення кута повороту і змінив на це, var rotateAngle = zero_start + (offsetAngle * i || 0);а також додав змінну для zero_start, тому якщо ви хочете почати з точки 270, а не з 0, або щось подібне. jsfiddle.net/q59s90oy/13 . Нарешті, я змінив CSS для елементів списку, щоб використовувати негативні поля. Серйозно, дякую, що поділилися роботою, дуже допомогли.
регулярний Джо

Це рад, радий, що ви змогли його налаштувати за потреби. Приємна варіація!
skwidbreth

1
Йо, це досить епічний спіральний ефект 😅 i.imgur.com/1VrubKC.png
Ітан,

@Ethan Ha ha yeah! Я люблю це робити! Я думав, що це може зробити крутий витвір мистецтва.
skwidbreth

5

Немає можливості чарівно розмістити елементи, які можна натискати, по колу навколо іншого елемента за допомогою CSS. Я б це зробив, використовуючи контейнер з position:relative;. А потім розмістіть усі елементи за position:absolute;допомогою topта за допомогою та leftдля націлювання на це місце.

Хоча ви не розміщували у ваших тегах може бути найкраще використовувати для цього jQuery / javascript.

Першим кроком є ​​ідеальне розміщення центрального зображення в центрі контейнера за допомогою position:relative;.

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

Після цього ви можете розмістити інші елементи навколо нього, використовуючи offset()centerImage мінус offset()контейнер. Надаючи точні topі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 
});

Те, що я тут зробив, - це розміщення елементів відносно centerImage. Сподіваюся, це допомагає.


5

Ви, звичайно, можете це зробити за допомогою чистого css або скористатися JavaScript. Моя пропозиція:

  • Якщо ви вже знаєте, що кількість зображень ніколи не зміниться, просто обчисліть свої стилі та використовуйте звичайний css (плюси: кращі характеристики, дуже надійний)

  • Якщо число може змінюватися або динамічно у вашому додатку, або просто може змінюватися в майбутньому, перейдіть за допомогою рішення Js (плюси: більш надійне)

У мене була подібна робота, тому я створив сценарій і відкрив його тут, на Github, для тих, кому він може знадобитися. Він просто приймає деякі значення конфігурації і просто виводить потрібний вам код CSS.

Якщо ви хочете скористатися рішенням Js, ось простий вказівник, який може бути вам корисний. Використовуючи цей html як вихідну точку, будучи #boxконтейнером та .dotзображенням / div посередині, ви хочете, щоб усі ваші інші зображення були навколо:

Запуск html:

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

Запуск 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;
}

Ви можете створити швидку функцію в таких напрямках:

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

Живий приклад ви можете побачити тут


4

Використовуючи рішення, запропоноване @Ana:

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

Я створив наступний jsFiddle, який розміщує кола динамічно за допомогою простого JavaScript (версія jQuery також доступна).

Принцип роботи досить простий:

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>


2

Ось версія, яку я зробив у React із прикладів тут.

Приклад 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>
  );
}

Чудова відповідь і чудовий фрагмент коду, проблема полягає лише в тому, що ви розмістили його у відповіді, яка не має нічого спільного з React!
Небрендовий Манчестер,

Я знаю, відповіді ніхто не просив, але ось вона все одно хе-хе :)
br3ntor

Я прийшов сюди, шукаючи рішення, яке я міг би використати в React, так все ще дуже корисному
Абхішек Касіреді

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.