<svg></svg>
body {
margin: 0;
display: flex;
justify-content: center;
background: #222;
}
svg {
align-self: center;
width: 100vmin; height: 100vmin;
box-shadow: 0 0 2px #000;
}
View Compiled
var C = .551915024494,
NS_URI = 'http://www.w3.org/2000/svg',
CUBIC_N = 3,
DURATION = 120 /* animation duration */,
N_LAYERS = 7 /* layers around midpoint */,
N_POLY = 6 /* type of poly distribution */,
PALETTE = [[220, 20, 60], [255, 215, 0]],
items = [] /* morphing shapes */,
n /* their count */,
timeline = [],
a /* amplitude of item jump */,
off /* base time offset */;
/* create an item morphing heart -> star */
var createItem = function(parent, layer) {
var pos /* positioner */, ani /* morpher */;
pos = document.createElementNS(NS_URI, 'g');
ani = document.createElementNS(NS_URI, 'path');
ani.setAttribute('d', timeline[0].d);
ani.setAttribute('fill', timeline[0].rgb);
pos.appendChild(ani);
parent.appendChild(pos);
items.push({ 'ani': ani, 'layer': layer });
return pos;
};
/* create polygonal distribution */
var createDistribution = function(container, bri) {
var bri = bri || 100,
α, be, lri, le, lrc, β, de, d, γ, x, y,
layer = N_LAYERS, frag, pos;
frag = document.createDocumentFragment();
α = 2*Math.PI/N_POLY; // central angle
be = 2*bri*Math.tan(.5*α); // base edge
while(layer--) {
if(layer) {
lri = layer*bri; // layer inradius
le = layer*be; // layer edge
lrc = lri/Math.cos(.5*α); // layer circumrad
/* take each edge */
for(var i = 0; i < N_POLY; i++) {
β = (i - .5)*α + .5*Math.PI;
for(var j = 0; j < layer; j++) {
de = (.5*layer - j)*be;
d = Math.hypot(lri, de);
γ = β + .5*α + Math.atan(de/lri);
x = d*Math.cos(γ);
y = d*Math.sin(γ);
pos = createItem(frag, layer);
pos.setAttribute(
'transform',
'translate(' + x + ' ' + y + ')'
);
}
}
}
else createItem(frag, 0);
}
container.appendChild(frag);
};
var getHeartPoints = function(d) {
var points = [], rp = .25*d, dv = .75*d,
kp = C*rp, xo = 2*rp,
kh = -rp - kp, kl = xo + kp;
points = [
[0, -rp],
[kp, kh], [xo - kp, kh], [xo, -rp],
[kl, kp - rp], [kl, rp - kp], [xo, rp],
[0, dv], [0, dv], [-xo, rp], // #3
[-kl, rp - kp], [-kl, kp - rp], [-xo, -rp],
[kp - xo, kh], [-kp, kh], [0, -rp]
];
return points;
};
var getStarPoints = function(d) {
var points = [],
n_penta = 5, nt = 2*n_penta,
α = 2*Math.PI/n_penta,
β = (n_penta - 2)*Math.PI/n_penta,
γ = Math.PI - β, δ = .5*α,
rin = d/(1 + Math.cos(δ) + Math.sin(δ)* Math.tan(γ)),
rout = d - rin,
φ, r, x, y;
for(var i = 0; i <= nt; i++) {
r = (i%2)?rout:rin;
φ = i*δ - .5*Math.PI;
x = r*Math.cos(φ);
y = r*Math.sin(φ);
points.push([x, y]);
if(i%2) points.push([x, y]); // ctrl pt
}
return points;
};
var getTimeline = function(keypoints, duration) {
var tl = [], n_curves = 5, pre = ['M'], np,
k, k1, d, rgb, x, y;
for(var i = 0; i < n_curves; i++) {
for(var j = 0; j < CUBIC_N; j++) {
pre.push(j ? ',' : 'C');
}
}
np = pre.length;
for(var i = 0; i <= duration; i++) {
k = i/duration;
k1 = 1 - k;
d = '';
rgb = [];
for(var j = 0; j < np; j++) {
x = k1*keypoints[0][j][0] + k*keypoints[1][j][0];
y = k1*keypoints[0][j][1] + k*keypoints[1][j][1];
d += pre[j] + Math.round(x) + ' ' + Math.round(y);
}
for(var j = 0; j < 3; j++) {
rgb.push(Math.round(k1*PALETTE[0][j] + k*PALETTE[1][j]));
}
tl.push({'d': d + 'z', 'rgb': 'rgb(' + rgb + ')'});
}
return tl;
};
var ani = function(t) {
var t_rel, d, φ, y, j, k, α;
/* go through all items */
for(var i = 0; i < n; i++) {
t_rel = (t - items[i].layer*off + DURATION)%DURATION;
d = 4*t_rel/DURATION;
φ = d*Math.PI;
y = -Math.round(a*Math.sin(φ));
if(y <= 0) {
j = 1 - Math.cos(φ);
k = Math.floor(d/2);
α = Math.round(j*90) + k*180;
j = ~~((k*1 + .5*j*Math.pow(-1, k))*.25*DURATION);
items[i].ani.setAttribute(
'transform',
'translate(0 ' + y + ') rotate(' + α + ')'
);
items[i].ani.setAttribute('d', timeline[j].d);
items[i].ani.setAttribute('fill', timeline[j].rgb);
}
}
requestAnimationFrame(ani.bind(this, ++t));
};
(function init() {
var svg, s, w, h, hdiag, bri, d;
/* get svg element, set a viewbox */
svg = document.querySelector('svg');
s = getComputedStyle(svg);
w = ~~s.width.split('px')[0];
h = ~~s.height.split('px')[0];
svg.setAttribute(
'viewBox' /* `viewbox` won't work! */,
[-w, -h, 2*w, 2*h]
);
/* compute stuff needed to create shapes */
hdiag = Math.hypot(w, h); // half diagonal
bri = hdiag/(N_LAYERS - .5) // base inradius for distr
a = d = .5*bri;
/* create timeline */
timeline = getTimeline(
[getHeartPoints(d), getStarPoints(d)],
.25*DURATION
);
/* create & position items */
createDistribution(svg, bri);
n = items.length;
off = Math.round(.25*DURATION/(.5*N_LAYERS));
/* start animation */
ani(0);
})();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.