Jak ustawić kilka <img>
elementów w kółko wokół innego i sprawić, by wszystkie te elementy były również klikalnymi linkami? Chcę, żeby wyglądało jak na poniższym obrazku, ale nie mam pojęcia, jak to osiągnąć.
Czy to w ogóle możliwe?
Oto bardziej nowoczesne rozwiązanie, z którego korzystam obecnie.
Zaczynam od wygenerowania kodu HTML, zaczynając od tablicy obrazów. Niezależnie od tego, czy HTML jest generowany za pomocą PHP, JS, jakiegoś preprocesora HTML, cokolwiek ... ma to mniejsze znaczenie, ponieważ podstawowa idea jest taka sama.
Oto kod mopsa, który mógłby to zrobić:
//- 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)
Wygenerowany kod HTML wygląda następująco (i tak, możesz też napisać kod HTML ręcznie, ale później wprowadzanie zmian będzie trudne):
<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>
Powiedzmy, że w CSS decydujemy o rozmiarze obrazów 8em
. Te --m
elementy są umieszczone na kole i to, czy są one w środku krawędzi wielokąta z --m
krawędziami, z których wszystkie są styczne do okręgu.
Jeśli trudno ci to sobie wyobrazić, możesz pobawić się tym interaktywnym demo, które tworzy okrąg i okrąg dla różnych wielokątów, których liczbę krawędzi wybierasz, przeciągając suwak.
To mówi nam, że rozmiar pojemnika musi być dwa razy większy od promienia okręgu plus dwa razy mniejszy od rozmiaru obrazów.
Nie znamy jeszcze promienia, ale możemy go obliczyć, jeśli znamy liczbę krawędzi (a zatem styczną połowy kąta bazowego, wstępnie obliczoną i ustawioną jako właściwość niestandardowa --tan
) i krawędź wielokąta. Prawdopodobnie chcemy, aby krawędź wielokąta była co najmniej wielkości obrazów, ale to, ile pozostawiamy po bokach, jest arbitralne. Powiedzmy, że mamy połowę rozmiaru obrazu z każdej strony, więc krawędź wielokąta jest dwukrotnie większa od rozmiaru obrazu. To daje nam następujący 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% }
Zobacz stare rozwiązanie, aby wyjaśnić, jak działa łańcuch transformacji.
W ten sposób dodanie lub usunięcie obrazu z tablicy obrazów automatycznie rozmieszcza nową liczbę obrazów w okręgu, tak aby były równo rozmieszczone, a także dostosowuje rozmiar kontenera. Możesz to sprawdzić w tym demo .
Tak, jest to bardzo możliwe i bardzo proste przy użyciu samego CSS. Musisz tylko mieć na uwadze kąty, pod którymi chcesz tworzyć linki z obrazami (dodałem na końcu fragment kodu, aby pokazać kąty za każdym razem, gdy najedziesz na jeden z nich).
Najpierw potrzebujesz opakowania. Ustawiłem jego średnicę na 24em
( width: 24em; height: 24em;
robi to), możesz ustawić ją na dowolną. Ty to dajesz position: relative;
.
Następnie umieść linki z obrazami w środku tego opakowania, zarówno w poziomie, jak i w pionie. Robisz to, ustawiając, position: absolute;
a następnie top: 50%; left: 50%;
i margin: -2em;
(gdzie 2em
jest połowa szerokości linku z obrazem, na który ustawiłem 4em
- znowu możesz to zmienić na cokolwiek chcesz, ale nie zapomnij zmienić marginesu w ta walizka).
Następnie decyduje o prostym, w którym chcesz mieć swoje powiązania z obrazami i dodać klasę deg{desired_angle}
(na przykład deg0
lub deg45
lub cokolwiek). Następnie dla każdej takiej klasy zastosujesz łańcuchowe transformacje CSS, na przykład:
.deg{desired_angle} {
transform: rotate({desired_angle}) translate(12em) rotate(-{desired_angle});
}
gdzie można wymienić {desired_angle}
się 0
, 45
i tak dalej ...
Pierwsza transformacja z obrotem obraca obiekt i jego osie, transformacja z obrotem powoduje przesunięcie obiektu wzdłuż obróconej osi X, a druga transformacja z obrotem przywraca obiekt na miejsce.
Zaletą tej metody jest to, że jest elastyczna. Możesz dodawać nowe obrazy pod różnymi kątami bez zmiany aktualnej struktury.
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>
Możesz także dodatkowo uprościć kod HTML, używając obrazów tła dla łączy zamiast img
tagów.
EDYCJA : przykład z rezerwą dla IE8 i starszych (testowane w IE8 i IE7)
Oto proste rozwiązanie bez pozycjonowania bezwzględnego:
.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/
źródło
Opierając się na doskonałej odpowiedzi @ Any, stworzyłem tę dynamiczną wersję, która pozwala dodawać i usuwać elementy z DOM oraz zachować proporcjonalne odstępy między elementami - sprawdź moje skrzypce: 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>
źródło
var rotateAngle = zero_start + (offsetAngle * i || 0);
Dodałem również zmienną dla zero_start, więc jeśli chcesz zacząć od punktu 270, a nie 0, lub coś podobnego. jsfiddle.net/q59s90oy/13 . Na koniec zmieniłem css dla elementów listy, aby używały ujemnych marginesów. Poważnie jednak, dzięki za podzielenie się pracą, bardzo pomogło.Nie ma możliwości magicznego umieszczenia klikalnych elementów w okręgu wokół innego elementu za pomocą CSS. Sposób, w jaki bym to zrobił, to użycie kontenera z
position:relative;
. A następnie umieść wszystkie elementy zaposition:absolute;
pomocątop
i używając ileft
aby wycelować w to miejsce.Nawet jeśli nie umieściłeś jquery w swoich tagach najlepiej do tego celu użyć jQuery / javascript.
Pierwszym krokiem jest idealne umieszczenie centralnego obrazu na środku pojemnika za pomocą
position:relative;
.#centerImage { position:absolute; top:50%; left:50%; width:200px; height:200px; margin: -100px 0 0 -100px; }
Następnie możesz umieścić inne elementy wokół niego, używając
offset()
of the centerImage minusoffset()
kontener. Co daje dokładnytop
ileft
obrazu.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 });
To, co tutaj zrobiłem, to umieszczenie elementów względem centerImage. Mam nadzieję że to pomoże.
źródło
Z pewnością możesz to zrobić za pomocą czystego CSS lub użyć JavaScript. Moja sugestia:
Jeśli już wiesz, że liczba zdjęć nigdy się nie zmieni, po prostu oblicz swoje style i korzystaj z prostego CSS (plusy: lepsza wydajność, bardzo niezawodna)
Jeśli liczba ta może się zmieniać dynamicznie w aplikacji lub może się zmieniać w przyszłości, wybierz rozwiązanie Js (zalety: bardziej przyszłościowe)
Miałem podobną pracę do wykonania, więc stworzyłem skrypt i otworzyłem go tutaj na Github dla każdego, kto mógł go potrzebować. Po prostu akceptuje niektóre wartości konfiguracyjne i po prostu wyświetla kod CSS, którego potrzebujesz.
Jeśli chcesz skorzystać z rozwiązania Js, oto prosty wskaźnik, który może Ci się przydać. Używając tego html jako punktu wyjścia, będąc
#box
kontenerem i.dot
obrazem / div w środku, wokół którego chcesz umieścić wszystkie inne obrazy:Uruchamianie html:
<div id="box"> <div class="dot"></div> <img src="my-img.jpg"> <!-- all the other images you need--> </div>
Początek 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; }
Możesz utworzyć szybką funkcję w następujący sposób:
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; }
Można zobaczyć na żywo przykład tutaj
źródło
Korzystając z rozwiązania zaproponowanego przez @Ana:
transform: rotate(${angle}deg) translate(${radius}px) rotate(-${angle}deg)
Stworzyłem następujący jsFiddle, który dynamicznie umieszcza okręgi przy użyciu zwykłego JavaScript (dostępna jest również wersja jQuery).
Sposób działania jest dość prosty:
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>
źródło
Oto wersja, którą stworzyłem w React na podstawie przykładów tutaj.
Przykład 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> ); }
źródło
Możesz to zrobić tak: skrzypce
Nie przejmuj się pozycjonowaniem, to szybki przykład
źródło