【发布时间】:2021-09-10 22:18:27
【问题描述】:
如何将多个<img> 元素定位成一个围绕另一个元素的圆圈,并使这些元素也都是可点击的链接?我希望它看起来像下面的图片,但我不知道如何实现这种效果。
这可能吗?
【问题讨论】:
如何将多个<img> 元素定位成一个围绕另一个元素的圆圈,并使这些元素也都是可点击的链接?我希望它看起来像下面的图片,但我不知道如何实现这种效果。
这可能吗?
【问题讨论】:
这是我最近使用的更现代的解决方案。
我首先从一组图像生成 HTML。是否使用 PHP、JS、一些 HTML 预处理器生成 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 边组成的多边形的边的中间,所有这些边都与圆相切。
如果您难以想象,您可以使用interactive demo,它为您通过拖动滑块选择边数的各种多边形构造内圆和外接圆。
这告诉我们容器的大小必须是圆半径的两倍加上图像大小的一半。
我们还不知道半径,但是如果我们知道边的数量(因此是底角一半的正切,预先计算并设置为自定义属性--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% }
有关转换链如何工作的说明,请参阅旧解决方案。
这样,在图像数组中添加或删除图像会自动将新数量的图像排列在一个圆圈上,使它们等距分布,并调整容器的大小。您可以在this demo 中进行测试。
是的,仅使用 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>
此外,您可以通过使用链接的背景图像而不是使用img 标签来进一步简化 HTML。
编辑:example with fallback for IE8 and older(在 IE8 和 IE7 中测试)
【讨论】:
这是没有绝对定位的简单解决方案:
.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>
【讨论】:
使用@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>
【讨论】:
在@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>
【讨论】:
var rotateAngle = zero_start + (offsetAngle * i || 0); 我还为 zero_start 添加了一个变量,因此如果您想从点 270 而不是 0 开始,或者类似的东西。 jsfiddle.net/q59s90oy/13。最后,我将列表项的 css 更改为使用负边距。 说真的,感谢分享工作,帮了大忙。
这是我根据此处的示例在 React 中制作的版本。
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>
);
}
【讨论】:
您当然可以使用纯 CSS 或 JavaScript 来实现。我的建议:
如果您已经知道图像数量永远不会改变,只需计算您的样式并使用普通 css(优点:性能更好,非常可靠)
如果数字可以在您的应用中动态变化,或者将来可能会发生变化,请使用 Js 解决方案(优点:更具前瞻性)
我有类似的工作要做,所以我创建了一个脚本并将其开源here on 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;
}
你可以看到一个活生生的例子here
【讨论】:
没有办法使用 CSS 神奇地将可点击的项目放置在另一个元素周围的圆圈中。
我将如何做到这一点的方法是使用带有position:relative; 的容器。然后使用position:absolute; 放置所有元素,并使用top 和left 定位它的位置。
即使您没有在标签中放置jquery,最好使用jQuery / javascript。
第一步是使用position:relative; 将您的中心图像完美地放置在容器的中心。
#centerImage {
position:absolute;
top:50%;
left:50%;
width:200px;
height:200px;
margin: -100px 0 0 -100px;
}
之后,您可以使用 centerImage 的 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
});
我在这里所做的是将元素 relative 放置到 centerImage。希望这会有所帮助。
【讨论】:
你可以这样做:fiddle
不要介意定位,这是一个简单的例子
【讨论】: