I want to arrange some rectangular div components around a regular polygon. Basically one of the long sides of the divs will be coincident with a line segment around the polygon.
In the final code, I’ll use .ejs (since the number of sides of the polygon is dynamic, 3-10 sides). In my “quick and dirty” testing I’m doing a triangle in just HTML and CSS to get the math right.
I have a “very close” solution already and am wondering how to get it “exact” and am also wondering why my geometry intuition is so far off.
HTML and CSS:
div {
position: absolute;
left: 200px;
top: 200px;
width: 80px;
height: 40px;
background-color: skyblue;
}
.rotatedA {
transform: translateY(-60px) translateX(-35px) rotate(300deg);
background-color: blue;
}
.rotatedB {
transform: translateY(-60px) translateX(35px) rotate(60deg);
background-color: red;
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
<link rel="stylesheet" href="basic.css">
</head>
<body>
<div>Normal</div>
<div class="rotatedA">Rotated</div>
<div class="rotatedB">Rotated</div>
</body>
</html>The first attempt I rotated “A” by 60 and “B” by -60 and did a translateY equal to the div height. When that did not work I played around with it. On this last attempt (close but not perfect since the rotations won’t give an integer) it seems like the Y adjustment is 1.5x (item height + cos(60)) but the X adjustment is 1/2 of sin(60) (I don’t understand why).
Since my results aren’t going to be an integer number of pixels what is the correct way to do this? Also, I don’t understand why my geometry is so off (I could understand sin(60) but 1/2(sin(60)) doesn’t make sense to me
Advertisement
Answer
Here’s a mathematical way; the number and dimensions are read by the script, then the divs are arranged accordingly. I also made sure that the wrapper container has the correct dimensions so it can be used with other elements:
function arrange(wrapper) {
wrapper.style.position = "relative";
const rects = Array.from(wrapper.children);
const n = rects.length;
/* dimensions of a rectangle */
const bb = rects[0].getBoundingClientRect();
const a = bb.width;
const h = bb.height;
/* incircle radius of regular polygon */
const r = a * 0.5 / Math.tan(Math.PI / n);
/* radius of outer circle */
const bigR = Math.sqrt((r + h) * (r + h) + a * a / 4);
rects.forEach((rect, i) => {
const angle = i * (360 / n);
if (angle) rect.style.transform = `rotate(${angle}deg)`;
rect.style.position = angle ? "absolute" : "relative";
rect.style.marginBottom = bigR + r + "px";
rect.style.transformOrigin = `${a/2}px ${-r}px`;
rect.style.left = bigR - a / 2 + "px";
rect.style.top = bigR + r + "px";
});
if (window.getComputedStyle(wrapper).display == "inline-block")
wrapper.style.width = 2 * bigR + "px";
}
arrange(document.querySelector('#polygon'));#polygon {
border: 1px solid black;
display: inline-block;
}
#polygon div {
width: 80px;
height: 20px;
background-color: skyblue;
text-align: center;
padding: 5px;
}<div id="polygon"> <div>Normal</div> <div>Rotated</div> <div>Rotated</div> <div>Rotated</div> <div>Rotated</div> <div>Rotated</div> <div>Rotated</div> </div>
The basic idea is to
- calculate the in-circle’s radius of the polygon based on the width of a rectangle
- set
transform-originaccordingly centered and above the first rectangle - arrange the others by rotating them
- (do more calculations so the wrapper element encompasses everything exactly)