Поместите значки в круг

96

Как я могу расположить несколько <img>элементов по кругу вокруг другого и сделать так, чтобы все эти элементы также были интерактивными ссылками? Я хочу, чтобы это выглядело как на картинке ниже, но я понятия не имею, как добиться этого эффекта.

Желаемый результат

Это вообще возможно?

Фатальный
источник

Ответы:

199

Решение 2020

Вот более современное решение, которое я использую сейчас.

Я начинаю с создания HTML, начиная с массива изображений. Генерируется ли HTML с использованием PHP, JS, какого-нибудь препроцессора HTML, что угодно ... это не так важно, поскольку основная идея остается той же.

Вот код Pug, который сделает это:

//- 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
Саджан Саркар
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/

gkond
источник
12

Основываясь на отличном ответе @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>

skwidbreth
источник
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
Итан
@ Этан Ха-ха! Я люблю это делать! Я думал, что из него получится классное произведение искусства.
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()центральное изображение минус 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>

Итай Грудев
источник
3

Вот версия, которую я сделал в 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>
  );
}
br3ntor
источник
Отличный ответ и отличный фрагмент кода, проблема только в том, что вы разместили его в ответе, который не имеет ничего общего с React!
Манчестер других производителей,
1
Я знаю, ответа никто не просил, но вот он, хе-хе :)
br3ntor
1
Я пришел сюда в поисках решения, которое можно было бы использовать в React, но оно все еще очень полезно
Абхишек Касиредди
1

Вы можете сделать это так: скрипка

Не обращайте внимания на позиционирование, это быстрый пример

отметка
источник