<div class="container">
<div class="bound"></div>
<div class="box-group">
<div class="box">1</div>
<div class="box">2</div>
<div class="box">3</div>
<div class="box">4</div>
<div class="box">5</div>
</div>
<input class="angle-control" type="range" min="0" max="360" value="40">
</div>
body {
margin: 0;
min-height: 100vh;
background:
linear-gradient(to right, #0001 1px, transparent 1px) 0 0 / 8px 8px,
linear-gradient(to bottom, #0001 1px, transparent 1px) 0 0 / 8px 8px;
font-family: monospace;
}
.container {
position: relative;
min-height: 100vh;
}
.angle-control {
position: fixed;
top: 20px;
left: 20px;
z-index: 999;
}
.bound {
position: absolute;
top: 0;
left: 0;
outline: 1px dashed tomato;
}
.box-group {
min-height: 100vh;
}
.box {
display: grid;
place-content: center;
position: absolute;
width: 100px;
height: 100px;
background-color: #ccc;
font-size: 2rem;
}
.box:nth-child(1) {
top: 50px;
left: 200px;
transform: rotate(15deg);
}
.box:nth-child(2) {
top: 150px;
left: 300px;
transform: rotate(25deg);
}
.box:nth-child(3) {
top: 80px;
left: 80px;
transform: translate(-25%, 50px) rotate(-20deg);
}
.box:nth-child(4) {
top: 250px;
left: 200px;
transform: rotate(45deg);
}
.box:nth-child(5) {
top: 20px;
left: 300px;
transform: translate(70%, 5px) rotate(-35deg);
}
import { vec2, mat2d, glMatrix } from 'https://cdn.skypack.dev/gl-matrix@3.4.3';
function getElOffset(el, offsetParent = document.body) {
let x = el.offsetLeft;
let y = el.offsetTop;
let op = el.offsetParent;
while (op !== offsetParent && op !== null) {
x += op.offsetLeft;
y += op.offsetTop;
op = op.offsetParent;
}
return [x, y];
}
function parseMatrix2D(str) {
// only for 'matrix(a, b, c, d, tx, ty)'
const [a, b, c, d, tx, ty] = str.slice(7, -1).split(',').map(parseFloat);
return mat2d.fromValues(a, b, c, d, tx, ty);
}
const container = document.querySelector('.container');
const angleRange = container.querySelector('.angle-control');
const bound = container.querySelector('.bound');
const boxes = container.querySelectorAll('.box');
const points = [boxes].flatMap((box, i) => {
const w = box.offsetWidth;
const h = box.offsetHeight;
const [x, y] = getElOffset(box, container);
const pts = [
vec2.fromValues(x, y),
vec2.fromValues(x + w, y),
vec2.fromValues(x + w, y + h),
vec2.fromValues(x, y + h),
];
const mat = parseMatrix2D(getComputedStyle(box).transform);
const origin = vec2.fromValues(x + w / 2, y + h / 2);
pts.forEach((p) => {
vec2.sub(p, p, origin);
vec2.transformMat2d(p, p, mat);
vec2.add(p, p, origin);
});
return pts;
});
function updateBbox(angle) {
const rotMat = mat2d.fromRotation(mat2d.create(), glMatrix.toRadian(angle));
const rotMatBack = mat2d.fromRotation(mat2d.create(), glMatrix.toRadian(-angle));
const rotBackPoints = points.map((p) => {
const np = vec2.create();
return vec2.transformMat2d(np, p, rotMatBack);
});
const { x0, x1, y0, y1 } = rotBackPoints.reduce(
(acc, [x, y]) => {
acc.x0 = Math.min(acc.x0, x);
acc.x1 = Math.max(acc.x1, x);
acc.y0 = Math.min(acc.y0, y);
acc.y1 = Math.max(acc.y1, y);
return acc;
},
{ x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity }
);
const bboxOrigin = vec2.fromValues((x0 + x1) / 2, (y0 + y1) / 2);
vec2.transformMat2d(bboxOrigin, bboxOrigin, rotMat);
const bboxCoord = vec2.fromValues(x0, y0);
vec2.transformMat2d(bboxCoord, bboxCoord, rotMat);
vec2.sub(bboxCoord, bboxCoord, bboxOrigin);
vec2.transformMat2d(bboxCoord, bboxCoord, rotMatBack);
vec2.add(bboxCoord, bboxCoord, bboxOrigin);
const [bx, by] = bboxCoord;
const bw = x1 - x0;
const bh = y1 - y0;
bound.style.transform = `translate(${bx}px, ${by}px) rotate(${angle}deg)`;
bound.style.width = `${bw}px`;
bound.style.height = `${bh}px`;
}
updateBbox(angleRange.valueAsNumber);
angleRange.addEventListener('input', event => {
updateBbox(event.target.valueAsNumber);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.