Skip to content

Instantly share code, notes, and snippets.

@DominikAngerer
Last active October 17, 2017 15:34
Show Gist options
  • Save DominikAngerer/3bae45d841de9c25d1cfe0b6853bf988 to your computer and use it in GitHub Desktop.
Save DominikAngerer/3bae45d841de9c25d1cfe0b6853bf988 to your computer and use it in GitHub Desktop.
Animated Cubes ES6
<html>
<head>
<title></title>
<style>
body, html {
background: #ccc;
}
#cube-container .cubes {
top: 0
}
#cube-container .cube {
position: absolute;
will-change: transform;
animation: cube-fade-in 2s cubic-bezier(.165, .84, .44, 1)
}
#cube-container .cube:first-child {
margin-top: -270px
}
#cube-container .cube:nth-child(2) {
z-index: 1;
margin-top: -265px
}
#cube-container .cube:nth-child(3) {
z-index: -1;
margin: -355px 0 0 50px
}
#cube-container .cube:nth-child(3)~.cube {
display: none
}
@media (min-width: 670px) {
#cube-container .cubes .cube {
margin: 0
}
#cube-container .cube:nth-child(3) {
z-index: 0
}
#cube-container .cube:nth-child(3)~.cube {
display: block
}
}
@keyframes cube-fade-in {
0% {
opacity: 0;
transform: scale(.5)
}
}
#cube-container .cube div {
position: absolute;
width: 100%;
height: 100%
}
#cube-container .cube .shadow {
top: 40%;
background: #07427a
}
#cube-container .cube .sides {
transform-style: preserve-3d;
perspective: 600px
}
#cube-container .cube .sides div {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
will-change: transform
}
</style>
</head>
<body>
<header>
<div class="stripes">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<template id="cube-template">
<div class="cube">
<div class="shadow"></div>
<div class="sides">
<div class="back"></div>
<div class="top"></div>
<div class="left"></div>
<div class="front"></div>
<div class="right"></div>
<div class="bottom"></div>
</div>
</div>
</template>
<div id="cube-container">
</div>
</header>
<script type="text/javascript">
const setState = (state, speed) =>
directions.forEach(axis => {
state[axis] += speed[axis];
if (Math.abs(state[axis]) < 360) return;
const max = Math.max(state[axis], 360);
const min = max == 360 ? Math.abs(state[axis]) : 360;
state[axis] = max - min;
});
const cubeIsHidden = left => left > parentWidth + 30;
// =================
// shared references
// =================
let Strut = {
random: function(t, o) {
return Math.random() * (o - t) + t
},
arrayRandom: function(t) {
return t[Math.floor(Math.random() * t.length)]
},
interpolate: function(t, o, e) {
return t * (1 - e) + o * e
},
rangePosition: function(t, o, e) {
return (e - t) / (o - t)
},
clamp: function(t, o, e) {
return Math.max(Math.min(t, e), o)
},
queryArray: function(t, o) {
return o || (o = document.body),
Array.prototype.slice.call(o.querySelectorAll(t))
},
ready: function(t) {
"loading" !== document.readyState ? t() : document.addEventListener("DOMContentLoaded", t)
}
}
let headerIsHidden = false;
const template = document.getElementById("cube-template");
const parent = document.getElementById("cube-container");
const getParentWidth = () => parent.getBoundingClientRect().width;
let parentWidth = getParentWidth();
window.addEventListener("resize", () => parentWidth = getParentWidth());
const directions = ["x", "y"];
const palette = {
white: {
color: [255, 255, 255],
shading: [160, 190, 218]
},
orange: {
color: [255, 250, 230],
shading: [255, 120, 50]
},
green: {
color: [205, 255, 204],
shading: [0, 211, 136]
}
};
// ==============
// cube instances
// ==============
const setCubeStyles = ({cube, size, left, top}) => {
Object.assign(cube.style, {
width: `${size}px`,
height: `${size}px`,
left: `${left}px`,
top: `${top}px`
});
Object.assign(cube.querySelector(".shadow").style, {
filter: `blur(${Math.round(size * .6)}px)`,
opacity: Math.min(size / 120, .4)
});
};
const createCube = size => {
const fragment = document.importNode(template.content, true);
const cube = fragment.querySelector(".cube");
const state = {
x: 0,
y: 0
};
const speed = directions.reduce((object, axis) => {
const max = size > sizes.m ? .3 : .6;
object[axis] = Strut.random(-max, max);
return object;
}, {});
const sides = Strut.queryArray(".sides div", cube).reduce((object, side) => {
object[side.className] = {
side,
hidden: false,
rotate: {
x: 0,
y: 0
}
};
return object;
}, {});
sides.top.rotate.x = 90;
sides.bottom.rotate.x = -90;
sides.left.rotate.y = -90;
sides.right.rotate.y = 90;
sides.back.rotate.y = -180
return {fragment, cube, state, speed, sides: Object.values(sides)};
};
const sizes = {
xs: 15,
s: 25,
m: 40,
l: 100,
xl: 120
};
const cubes = [
{
tint: palette.green,
size: sizes.xs,
left: 35,
top: 465
},{
tint: palette.white,
size: sizes.s,
left: 55,
top: 415
},{
tint: palette.white,
size: sizes.xl,
left: 140,
top: 400
},{
tint: palette.white,
size: sizes.m,
left: 420,
top: 155
},{
tint: palette.green,
size: sizes.xs,
left: 440,
top: 280
},{
tint: palette.orange,
size: sizes.s,
left: 480,
top: 228
},{
tint: palette.white,
size: sizes.l,
left: 580,
top: 255
},{
tint: palette.green,
size: sizes.s,
left: 780,
top: 320
},{
tint: palette.white,
size: sizes.xl,
left: 780,
top: 120
},{
tint: palette.orange,
size: sizes.l,
left: 900,
top: 310
},{
tint: palette.green,
size: sizes.m,
left: 1030,
top: 200
}
].map(object => Object.assign(createCube(object.size), object));
cubes.forEach(setCubeStyles);
// =======================
// cube rotating animation
// =======================
const getDistance = (state, rotate) =>
directions.reduce((object, axis) => {
object[axis] = Math.abs(state[axis] + rotate[axis]);
return object;
}, {});
const getRotation = (state, size, rotate) => {
const axis = rotate.x ? "Z" : "Y";
const direction = rotate.x > 0 ? -1 : 1;
return `
rotateX(${state.x + rotate.x}deg)
rotate${axis}(${direction * (state.y + rotate.y)}deg)
translateZ(${size / 2}px)
`;
};
const getShading = (tint, rotate, distance) => {
const darken = directions.reduce((object, axis) => {
const delta = distance[axis];
const ratio = delta / 180;
object[axis] = delta > 180 ? Math.abs(2 - ratio) : ratio;
return object;
}, {});
if (rotate.x)
darken.y = 0;
else {
const {x} = distance;
if (x > 90 && x < 270)
directions.forEach(axis => darken[axis] = 1 - darken[axis]);
}
const alpha = (darken.x + darken.y) / 2;
const blend = (value, index) => Math.round(Strut.interpolate(value, tint.shading[index], alpha));
const [r, g, b] = tint.color.map(blend);
return `rgb(${r}, ${g}, ${b})`;
};
const shouldHide = (rotateX, x, y) => {
if (rotateX)
return x > 90 && x < 270;
if (x < 90)
return y > 90 && y < 270;
if (x < 270)
return y < 90;
return y > 90 && y < 270;
};
const updateSides = ({state, speed, size, tint, sides, left}) => {
if (headerIsHidden || cubeIsHidden(left)) return;
const animate = object => {
const {side, rotate, hidden} = object;
const distance = getDistance(state, rotate);
// don't animate hidden sides
if (shouldHide(rotate.x, distance.x, distance.y)) {
if (!hidden) {
side.hidden = true;
object.hidden = true;
}
return;
}
if (hidden) {
side.hidden = false;
object.hidden = false;
}
side.style.transform = getRotation(state, size, rotate);
side.style.backgroundColor = getShading(tint, rotate, distance);
};
setState(state, speed);
sides.forEach(animate);
};
const reduceMotion = matchMedia("(prefers-reduced-motion)").matches;
const tick = () => {
cubes.forEach(updateSides);
if (reduceMotion) return;
requestAnimationFrame(tick);
};
const container = document.createElement("div");
container.className = "cubes";
cubes.forEach(({fragment}) => container.appendChild(fragment));
const start = () => {
tick();
parent.appendChild(container);
};
Strut.ready(() => {
start()
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment