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-origin
accordingly centered and above the first rectangle - arrange the others by rotating them
- (do more calculations so the wrapper element encompasses everything exactly)