<div id="cube-container"></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>
HTML
格式化
支持Emmet,输入 p 后按 Tab键试试吧!
<head> ... </head>
<body>
</body>
.cubes
{
.cube
{
position: absolute;
height: 100px;
width: 100px;
margin: 0;
animation: cube-fade-in 2s cubic-bezier(.165, .84, .44, 1);
will-change: transform;
@keyframes cube-fade-in
{
0%
{
opacity: 0;
transform: scale(.5)
}
}
*
{
position: absolute;
height: 100%;
width: 100%;
}
.shadow
{
background: #07427a;
top: 40%;
}
.sides
{
transform-style: preserve-3d;
perspective: 600px;
div
{
backface-visibility: hidden;
will-change: transform;
}
.front {
transform: rotateY(0deg) translateZ(50px);
}
.back {
transform: rotateY(-180deg) translateZ(50px);
}
.left {
transform: rotateY(-90deg) translateZ(50px);
}
.right {
transform: rotateY(90deg) translateZ(50px);
}
.top {
transform: rotateX(90deg) translateZ(50px);
}
.bottom {
transform: rotateX(-90deg) translateZ(50px);
}
}
}
}
"use strict";
// Utility functions
var utils = {
// Random float between min and max
random: function(min, max) {
return Math.random() * (max - min) + min;
},
// Returns a random item from an array
arrayRandom: function(arr) {
return arr[ Math.floor( Math.random() * arr.length ) ];
},
// Linear interpolation between a and b
// Ex: (100, 200, 0.5) = 150
interpolate: function(a, b, i) {
return a*(1-i) + b*i;
},
// querySelectorAll as an array
queryArray: function(selector, node) {
if (!node) node = document.body;
return Array.prototype.slice.call(node.querySelectorAll(selector));
}
};
// =======
// helpers
// =======
//state is state of rotation (angle) for x and y
const setState = (state, speed) =>
directions.forEach(axis => {
state[axis] += speed[axis];
// console.log('statex: ' + state.x);
// console.log('statey ' + state.y);
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;
// console.log('statex: ' + state.x);
});
const cubeIsHidden = left => left > parentWidth + 30;
// var cubeIsHidden = function cubeIsHidden(left) {
// return left > parentWidth + 30;
// };
// =================
// shared variables
// =================
const template = document.getElementById("cube-template");
const parent = document.getElementById("cube-container");
// get width of cube container
const getParentWidth = () => parent.getBoundingClientRect().width;
let parentWidth = getParentWidth();
// update parent width on resize
window.addEventListener("resize", () => parentWidth = getParentWidth());
const directions = ["x", "y"];
// data for colours and sizes
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]
}
};
const sizes = {
xs: 15,
s: 25,
m: 40,
l: 100,
xl: 120
};
const testing = () => {
console.log('testing')
}
testing();
// ==============
// cube instances
// ==============
const setCubeStyles = ({cube, size, left, top}) => {
// assigning size and position values
Object.assign(cube.style, {
width: `${size}px`,
height: `${size}px`,
left: `${left}px`,
top: `${top}px`
});
Object.assign(cube.querySelector(".shadow").style, {
// assigning shadow blur and opacity value according to size
filter: `blur(${Math.round(size * .6)}px)`,
opacity: Math.min(size / 120, .4)
});
};
const createCube = size => {
// The Document method importNode() creates a new copy of the specified Node or DocumentFragment from another document so that it can be inserted into the current Document. It is not yet included in the document tree; to do that, you need to call a method such as appendChild() or insertBefore(). Here we are importing the html content of the cube template, and from that import, selecting the cube div
// https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode
const fragment = document.importNode(template.content, true);
const cube = fragment.querySelector(".cube");
const state = {
x: 0,
y: 0
};
//calculating speed of rotation
const speed = directions.reduce((object, axis) => {
const max = size > sizes.m ? .3 : .6;
object[axis] = utils.random(-max, max);
return object;
}, {});
const sides = utils.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)};
};
// building cubes data with palette and size
// could be randomized?
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));
// for each item in the array, build a cube with a createCube method and assign it styles
cubes.forEach(setCubeStyles);
// =======================
// cube rotating animation
// =======================
// Adding initial position and rotation
const getDistance = (state, rotate) =>
directions.reduce((object, axis) => {
object[axis] = Math.abs(state[axis] + rotate[axis]);
return object;
}, {});
// or
// const getDistance = (state, rotate) => {
// return {
// x: Math.abs(state.x + rotate.x),
// y: Math.abs(state.y + rotate.y)
// }
// }
// or
// function getDistance(state, rotate) {
// const object = {};
// for (let axis of ['x', 'y']) {
// object[axis] = Math.abs(state[axis] + rotate[axis]);
// }
// return object;
// }
// calculate the rotation value
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;
// }, {});
const darken = {};
for (let axis of ['x', 'y']) {
const delta = distance[axis];
const ratio = delta / 180;
darken[axis] = delta > 180 ? Math.abs(2 - ratio) : ratio;
}
// console.log(darken);
if (rotate.x)
darken.y = 0;
else {
const {x} = distance; //look up for ES5 syntax
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(utils.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 ( 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 tick = () => {
cubes.forEach(updateSides);
requestAnimationFrame(tick);
};
// ===============
// parallax scroll
// ===============
// give it some extra space to account for the parallax and the shadows of the cubes
// const parallaxLimit = parent.getBoundingClientRect().height + 80;
// window.addEventListener("scroll", () => {
// const scroll = window.scrollY;
// if (scroll < parallaxLimit) {
// cubes.forEach(({cube, speed}) =>
// cube.style.transform = `translateY(${Math.abs(speed.x * .5) * scroll}px)`);
// return;
// }
// });
// ==========
// initialize
// ==========
const container = document.createElement("div");
container.className = "cubes";
cubes.forEach(({fragment}) => container.appendChild(fragment));
const start = () => {
tick();
parent.appendChild(container);
};
"requestIdleCallback" in window
? requestIdleCallback(start)
: start();