<div class="anim" id="anim">
<img src="https://dl.dropboxusercontent.com/s/0zdz0tfet3w0ov0/2ecf462b1b.png" id="card0" class="card0 card" crossorigin="anonymous">
<div class="shadow1 shadow"></div>
<img src="https://dl.dropboxusercontent.com/s/5mcga437ozwplst/d6f0903c43.png" id="card1" class="card1 card" crossorigin="anonymous">
<div class="shadow2 shadow"></div>
<img src="https://dl.dropboxusercontent.com/s/z5vmbn98kog5klh/de31d264c2.png" id="card2" class="card2 card" crossorigin="anonymous">
<div class="title">CSS</div>
</div>
$width: 336;
$height: 460;
body { box-sizing: border-box; margin: 0; background: grey; }
#anim {
position: absolute;
width: $width*1px; height: $height*1px;
left: 356px; top: 0;
perspective: 1500px;
perspective-origin: 50% -30%;
}
.anim .card, .anim .shadow {
position: absolute;
display: block;
left: 0; top: 0;
width: $width*1px; height: $height*1px;
transform-origin: 0 0;
transform: scale(0.84);
}
.anim .shadow {
opacity: 0.6;
}
#anim .title {
position: absolute;
display: block;
left: 0.05 * $width * 1px; top: 0.7 * $height * 1px;
width: 0.9 * $width * 1px; height: 0.2 * $height * 1px;
border: 1.5px solid #9d8275;
background-color: rgba(0,0,0,0.65);
color: #fff;
font: $width*0.15px "Proxima Nova Cn Rg";
line-height: 0.2 * $height * 1px;
text-align: center;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
$progress: 1.0;
$xOffset: 0.08*$width;
.anim .card0 { animation: fanout_card0 1s infinite alternate; }
.anim .shadow1 { animation: fanout_shadow1 1s infinite alternate; }
.anim .card1 { animation: fanout_card1 1s infinite alternate; }
.anim .shadow2 { animation: fanout_shadow2 1s infinite alternate; }
.anim .card2 { animation: fanout_card2 1s infinite alternate; }
@keyframes fanout_card0 {
from {transform: skewY(0deg) rotateY(0 * 0 * 0.25 * -90deg) translateX($xOffset*2*1px) scale(0.84); }
to { transform: skewY(0deg) rotateY(1 * 0 * 0.25 * -90deg) translateX($xOffset*2*1px) scale(0.84); }
}
@keyframes fanout_shadow1 {
from {transform: skewY(0deg) rotateY(0 * 1 * 0.20 * -90deg) translateX($xOffset*1*1px) scale(0.84); }
to { transform: skewY(5deg) rotateY(1 * 1 * 0.20 * -90deg) translateX($xOffset*1*1px) scale(0.84); }
}
@keyframes fanout_card1 {
from {transform: skewY(0deg) rotateY(0 * 1 * 0.25 * -90deg) translateX($xOffset*1*1px) scale(0.84); }
to { transform: skewY(0deg) rotateY(1 * 1 * 0.25 * -90deg) translateX($xOffset*1*1px) scale(0.84); }
}
@keyframes fanout_shadow2 {
from {transform: skewY(0deg) rotateY(0 * 2 * 0.20 * -90deg) translateX($xOffset*0*1px) scale(0.84); }
to { transform: skewY(10deg) rotateY(1 * 2 * 0.20 * -90deg) translateX($xOffset*0*1px) scale(0.84); }
}
@keyframes fanout_card2 {
from {transform: rotateY(0 * 2 * 0.25 * -90deg) translateX($xOffset*0*1px) scale(0.84); }
to { transform: rotateY(1 * 2 * 0.25 * -90deg) translateX($xOffset*0*1px) scale(0.84); }
}
function getAlphaAsCanvas(img) {
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, img.width, img.height);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(img, 0, 0);
return canvas;
}
function blurShadow(img, amount) {
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
var total = amount;
while(amount--) {
var alpha = (amount+1) / total;
var scale = 1+(1-((amount+1) / total))*total/img.height*2;
ctx.globalAlpha = alpha;
ctx.translate(-0.5*(scale*img.width-img.width), -0.5*(scale*img.height-img.height));
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
ctx.setTransform(1,0,0,1,0,0);
}
return canvas;
}
function paintName(ctx, name) {
var w = ctx.canvas.width;
var h = ctx.canvas.height;
ctx.beginPath();
ctx.lineWidth = 1.5;
ctx.strokeStyle = '#9d8275';
ctx.fillStyle = 'rgba(0,0,0,0.65)';
ctx.rect(
w * 0.05, h * 0.7,
w * 0.9, h * 0.2
);
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#fff';
ctx.font = (w*0.15)+'px "Proxima Nova Cn Rg"';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.strokeStyle = '#000';
ctx.lineWidth = 4;
ctx.strokeText(name, w * 0.5, h * 0.8);
ctx.fillText(name, w * 0.5, h * 0.8);
}
function paintCards(ctx, imgs, alpha, progress) {
var w = ctx.canvas.width;
var h = ctx.canvas.height;
function rotateCard(ctx, img, xOffset, yOffset, progress, shift) {
shift = shift || 0;
var scale = 0.84;
var dw = scale * Math.min(1, Math.max(0, Math.cos(progress * Math.PI/2))) * w;
var sh = h;
var slice_sw = 1 * w / dw;
var x = dw; while(x-- > 0) {
var sx = x / dw * w;
var dx = x + xOffset;
var dh = x * 0.3 * progress / dw;
var dy = (-0.5 + shift) * dh;
ctx.drawImage(img,
sx, 0, slice_sw, sh,
dx, dy * scale * h + yOffset, 1, (1 + dh) * scale * h
);
}
}
for(var i = 0; i < imgs.length; i++) {
var img = imgs[i];
if(i > 0) { // Skip shadow for bottom card
ctx.globalAlpha = 0.6;
ctx.globalCompositeOperation = 'source-atop';
rotateCard(ctx, alpha, (2-i) * (0.08*w), 0, progress * i * 0.20, 2);
}
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
rotateCard(ctx, img, (2-i) * (0.08*w), 0, progress * i * 0.25, 1);
}
}
function startAnimation(ctx, name, imgs) {
var frame = 0;
var alpha = blurShadow(getAlphaAsCanvas(imgs[0]), 10);
Array.prototype.forEach.call(
document.getElementsByClassName('shadow'), function(el) {
var shadow = document.createElement('canvas');
shadow.width = alpha.width;
shadow.height = alpha.height;
shadow.getContext('2d').drawImage(alpha, 0, 0);
el.appendChild(shadow);
}
);
function drawFrame() {
window.requestAnimationFrame(drawFrame);
frame++;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
paintCards(ctx, imgs, alpha, Math.sin((frame % 120) / 120 * Math.PI));
paintName(ctx, name);
}
window.requestAnimationFrame(drawFrame);
}
function main() {
console.clear();
var width = 336;
var height = width * 460/336;
return Promise.all(
['card0', 'card1', 'card2']
.map(function(id) { return imgToPromise(document.getElementById(id)); })
)
.then(function(imgs) {
var canvas = document.createElement('canvas');
canvas.width = width || 336;
canvas.height = height || 460;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
startAnimation(ctx, 'Canvas', imgs);
})
}
document.addEventListener('DOMContentLoaded', main);
// Helpers
function imgToPromise(img) {
return new Promise(function(resolve, reject) {
if(img.complete) return resolve(img);
img.onload = function() {
resolve(img);
};
img.onerror = function() {
reject(new Error('Error loading image: '+img.src));
};
});
}
Also see: Tab Triggers