Háttéranimációk
Aranykockák – Térbeli összeállás ✨
Élő előnézet – az animáció teljes méretben.
Élő előnézet
HTML · CSS · JS
<div class="we-cubes">
<svg class="we-cubes-lines"
viewBox="0 0 1440 900"
preserveAspectRatio="xMidYMid slice"
aria-hidden="true">
<path d="M-100,250 C180,70 300,420 550,300 S900,70 1120,240 S1390,430 1540,180"/>
<path d="M-100,650 C160,490 350,790 600,630 S950,430 1160,620 S1400,760 1540,550"/>
<path d="M160,-100 C350,140 150,340 360,490 S650,730 510,1000"/>
</svg>
<div class="we-cubes-stage"></div>
</div>
.we-cubes {
--we-cube-size: 42px;
--we-cube-gold: #c9a96e;
position: relative;
width: 100%;
min-height: 460px;
overflow: hidden;
isolation: isolate;
perspective: 1200px;
}
.we-cubes-lines {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.we-cubes-lines path {
fill: none;
stroke: var(--we-cube-gold);
stroke-width: .65;
stroke-dasharray: 1800;
stroke-dashoffset: 1800;
opacity: .5;
animation: weCubesLineFlow 12s ease-in-out infinite alternate;
}
.we-cubes-lines path:nth-child(2) {
animation-delay: -4s;
opacity: .32;
}
.we-cubes-lines path:nth-child(3) {
animation-delay: -7s;
opacity: .22;
}
@keyframes weCubesLineFlow {
0% {
stroke-dashoffset: 1800;
opacity: .12;
}
50% {
opacity: .5;
}
100% {
stroke-dashoffset: 0;
opacity: .25;
}
}
.we-cubes-stage {
position: absolute;
left: 50%;
top: 50%;
width: 1px;
height: 1px;
z-index: 1;
transform-style: preserve-3d;
transform: translate(-50%, -50%) rotateX(-13deg) rotateY(-24deg);
animation: weCubesDrift 8s ease-in-out infinite;
}
@keyframes weCubesDrift {
0%, 100% {
margin-top: 0;
rotate: 0deg;
}
50% {
margin-top: -7px;
rotate: 1deg;
}
}
.we-cubes-piece {
position: absolute;
left: 0;
top: 0;
width: var(--we-cube-size);
height: var(--we-cube-size);
transform-style: preserve-3d;
opacity: 0;
will-change: transform, opacity;
}
.we-cubes-face {
position: absolute;
inset: 0;
border: 1px solid rgba(224, 190, 123, .76);
background: linear-gradient(
145deg,
rgba(201, 169, 110, .18),
rgba(23, 31, 51, .48) 58%,
rgba(201, 169, 110, .09)
);
box-shadow:
inset 0 0 12px rgba(201, 169, 110, .045),
0 0 7px rgba(201, 169, 110, .035);
}
.we-cubes-face.front {
transform: translateZ(calc(var(--we-cube-size) / 2));
}
.we-cubes-face.back {
transform: rotateY(180deg) translateZ(calc(var(--we-cube-size) / 2));
}
.we-cubes-face.right {
transform: rotateY(90deg) translateZ(calc(var(--we-cube-size) / 2));
}
.we-cubes-face.left {
transform: rotateY(-90deg) translateZ(calc(var(--we-cube-size) / 2));
}
.we-cubes-face.top {
transform: rotateX(90deg) translateZ(calc(var(--we-cube-size) / 2));
}
.we-cubes-face.bottom {
transform: rotateX(-90deg) translateZ(calc(var(--we-cube-size) / 2));
}
@media (max-width: 600px) {
.we-cubes {
--we-cube-size: 30px;
min-height: 380px;
}
.we-cubes-stage {
transform: translate(-50%, -50%) scale(.78) rotateX(-13deg) rotateY(-24deg);
}
}
(() => {
document.querySelectorAll(".we-cubes").forEach(root => {
if (root.dataset.initialized === "true") return;
root.dataset.initialized = "true";
const stage = root.querySelector(".we-cubes-stage");
const ASSEMBLE_TIME = 3800;
const HOLD_TIME = 2400;
const DISASSEMBLE_TIME = 3100;
const SCATTER_HOLD = 1000;
let cubes = [];
let frameId = 0;
let timerId = 0;
let destroyed = false;
const rand = (min, max) =>
Math.random() * (max - min) + min;
const ease = t =>
t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2;
function inside(x, y, z, cx, cy, cz, rx, ry, rz) {
return (
((x - cx) / rx) ** 2 +
((y - cy) / ry) ** 2 +
((z - cz) / rz) ** 2 <= 1
);
}
function makeShape() {
const points = [];
for (let y = -3; y <= 3; y++) {
for (let z = -2; z <= 2; z++) {
for (let x = -5; x <= 5; x++) {
const included =
inside(x, y, z, 0, 0, 0, 4.8, 2.65, 1.8) ||
inside(x, y, z, 2.2, 1.8, 0, 2.5, 1.6, 1.4) ||
inside(x, y, z, -2.7, -1.8, 0, 2.1, 1.5, 1.3) ||
inside(x, y, z, -4.2, .1, .1, 1.5, 1.25, 1.25);
if (!included) continue;
const hash = Math.abs(
(x * 73856093) ^
(y * 19349663) ^
(z * 83492791)
) % 10;
const core = Math.abs(x) <= 1 && Math.abs(y) <= 1;
if (!core && hash < 4) continue;
points.push({ x, y, z });
}
}
}
return points;
}
function createCube(point) {
const element = document.createElement("div");
element.className = "we-cubes-piece";
["front", "back", "right", "left", "top", "bottom"]
.forEach(name => {
const face = document.createElement("div");
face.className = `we-cubes-face ${name}`;
element.appendChild(face);
});
stage.appendChild(element);
const angle = rand(0, Math.PI * 2);
const radius = rand(100, 280);
const size = parseFloat(
getComputedStyle(root).getPropertyValue("--we-cube-size")
) || 42;
return {
element,
target: {
x: point.x * size * .91,
y: -point.y * size * .91,
z: point.z * size * .91
},
scatter: {
x: Math.cos(angle) * radius + rand(-80, 80),
y: Math.sin(angle) * radius * .72 + rand(-70, 70),
z: rand(-150, 150),
rx: rand(-180, 180),
ry: rand(-180, 180),
rz: rand(-180, 180)
},
delay: rand(0, 420)
};
}
function position(c, x, y, z, rx, ry, rz, opacity) {
c.element.style.opacity = opacity;
c.element.style.transform =
`translate3d(${x}px, ${y}px, ${z}px) ` +
`rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`;
}
function assemble(start) {
cancelAnimationFrame(frameId);
function tick(now) {
if (destroyed) return;
const elapsed = now - start;
let done = true;
cubes.forEach(c => {
const progress = Math.min(
1,
Math.max(0, elapsed - c.delay) / ASSEMBLE_TIME
);
const t = ease(progress);
if (progress < 1) done = false;
const a = c.scatter;
const b = c.target;
position(
c,
a.x * (1 - t) + b.x * t,
a.y * (1 - t) + b.y * t,
a.z * (1 - t) + b.z * t,
a.rx * (1 - t),
a.ry * (1 - t),
a.rz * (1 - t),
Math.min(1, progress * 2.5)
);
});
if (!done) {
frameId = requestAnimationFrame(tick);
} else {
timerId = setTimeout(
() => disassemble(performance.now()),
HOLD_TIME
);
}
}
frameId = requestAnimationFrame(tick);
}
function disassemble(start) {
cancelAnimationFrame(frameId);
function tick(now) {
if (destroyed) return;
const elapsed = now - start;
let done = true;
cubes.forEach(c => {
const progress = Math.min(
1,
Math.max(0, elapsed - c.delay * .45) /
DISASSEMBLE_TIME
);
const t = ease(progress);
if (progress < 1) done = false;
const a = c.target;
const b = c.scatter;
position(
c,
a.x * (1 - t) + b.x * t,
a.y * (1 - t) + b.y * t,
a.z * (1 - t) + b.z * t,
b.rx * t,
b.ry * t,
b.rz * t,
1 - Math.max(0, (progress - .72) / .28)
);
});
if (!done) {
frameId = requestAnimationFrame(tick);
} else {
timerId = setTimeout(
() => assemble(performance.now()),
SCATTER_HOLD
);
}
}
frameId = requestAnimationFrame(tick);
}
function start() {
cancelAnimationFrame(frameId);
clearTimeout(timerId);
stage.replaceChildren();
cubes = makeShape().map(createCube);
assemble(performance.now());
}
start();
if ("ResizeObserver" in window) {
let lastWidth = root.clientWidth;
const observer = new ResizeObserver(() => {
if (root.clientWidth === lastWidth) return;
lastWidth = root.clientWidth;
start();
});
observer.observe(root);
}
});
})();