Skip to content

Instantly share code, notes, and snippets.

@ndhu
Last active December 28, 2015 03:49
Show Gist options
  • Save ndhu/7437850 to your computer and use it in GitHub Desktop.
Save ndhu/7437850 to your computer and use it in GitHub Desktop.
A Pen by Andy Andreas Hulstkamp.
<body class="terrain">
<h1 class="heading">Retrowaders</h1>
<p>TINKERTINKER</p>
<p>wing-like structures generatively painted on a canvas, then reflected and animated using CSS3-features to form Invaders-like manoeuvres</p>
<p><a href="https://twitter.com/andyhulstkamp">@andyhulstkamp</a></p>
<div class="birds"></div>
<div class="regenerate">generate</div>
</body>
(function (window, undefined) {
"use strict";
var flythings = function () {
var ctr = 0,
colors = ["rgba(64,64,64,1)", "rgba(255,32,32,1)", "rgba(32,32,55,1)", "rgba(32,64,255,1)", "rgba(255,129,5,1)"],
grid = [],
fleet;
var setup = function () {
var regenerate,
terrain;
createGrid2();
terrain = document.querySelector('.terrain');
createBackground(terrain);
regenerate = document.querySelector(".regenerate");
regenerate.addEventListener("click", function () {
generate();
});
generate();
};
var generate = function (holder, ctx) {
if (fleet) {
fleet.destroy();
}
fleet = new Fleet(holder);
};
var Wing = function (holder, grid, w, h, colors) {
this.holder = holder;
this.grid = grid;
this.w = w;
this.h = h;
this.colors = colors;
this.canvas = null;
this.ctx = null;
this.paintWing(holder, grid, colors);
};
Wing.prototype = {
paintWing: function (holder, grid, colors) {
var canvas = this.canvas = this.createCanvas(holder, this.w, this.h);
var ctx = this.ctx = this.createContext(canvas, this.w, this.h);
var clr1 = colors[Math.round(Math.random() * (colors.length - 1))];
var clr2 = colors[Math.round(Math.random() * (colors.length - 1))];
this.drawTriangle(clr1, 0);
this.drawTriangle(clr2);
this.drawTriangle(clr1);
this.drawTriangle(clr1);
this.drawTriangle(clr2);
},
createCanvas: function (holder, w, h) {
var c = document.createElement('canvas');
c.setAttribute("width", w + 'px');
c.setAttribute("height", h + 'px');
if (holder) {
holder.appendChild(c);
}
return c;
},
createContext: function (c, w, h) {
var ctx = c.getContext('2d');
fill(ctx, w, h, 'rgba(0,0,0,0)');
return ctx;
},
drawTriangle: function (clr, y) {
var w, h, p1, p2, p3, ctx, grid;
ctx = this.ctx;
grid = this.grid;
w = ctx.canvas.width;
h = ctx.canvas.height;
p1 = {
x: 0,
y: y !== undefined ? y : Math.floor(Math.random() * h)
};
p2 = grid[Math.round(Math.random() * (grid.length - 1))];
p2.x = p2.x * w / 14;
p2.y = p2.y * h / 25;
p3 = {
x: 0,
y: Math.floor(Math.random() * h)
};
ctx.fillStyle = clr;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.lineTo(p3.x, p3.y);
ctx.fill();
ctx.closePath();
},
getReflection: function () {
var c = this.createCanvas(null, this.w, this.h);
var ctx = this.createContext(c, this.w, this.h);
//mirror
ctx = c.getContext("2d");
ctx.clearRect(0, 0, this.w, this.h);
ctx.translate(this.w, 0);
ctx.scale(-1, 1);
ctx.drawImage(this.canvas, 0, 0);
return c;
}
};
var Fleet = function (holder, nShips) {
this.holder = holder;
this.fleetNode = document.body.querySelector(".birds");
this.nShips = nShips || 9;
this.ships = [];
this.create(holder);
};
Fleet.prototype = {
create: function (holder) {
this.buildParts();
this.updateFormation();
},
buildParts: function (holder) {
var w1,
w2;
w1 = new Wing(null, grid, 400, 400, colors);
w2 = w1.getReflection();
var rightWing = w1.canvas.toDataURL();
var leftWing = w2.toDataURL();
for (var i = 0; i < this.nShips; i++) {
var name = "A" + ctr++;
var thing = new FlyThing(i, name, leftWing, rightWing);
this.ships.push(thing);
this.fleetNode.appendChild(thing.domNode);
}
},
updateFormation: function () {
var that = this,
ships = this.ships,
nShips = ships.length;
var update = function () {
var formation = Math.round(Math.random() * 5),
ship;
for (var i = 0; i < nShips; i++) {
ship = ships[i];
ship.setFormation(formation);
}
};
update();
var t = setInterval(update, 3000);
},
destroy: function () {
for (var i = 0; i < this.nShips; i++) {
var ship = this.ships[i];
ship.destroy();
}
var children = this.fleetNode.childNodes;
while (children.length > 0) {
this.fleetNode.removeChild(children[0]);
}
}
};
var FlyThing = function (index, name, wing1, wing2) {
this.index = index;
this.domNode = null;
this.body = null;
this.name = name;
this.create(name, wing1, wing2);
};
FlyThing.prototype = {
/**
* creates the FlyThing
* @param name
* @param wing1 base64 image of the left wing
* @param wing2 base64 image of the (reflected) right wing
*/
create: function (name, wing1, wing2) {
this.body = this.createBody(name);
this.domNode = document.createDocumentFragment('div');
this.domNode.name = "sprite " + name;
this.domNode.appendChild(this.body);
this.buildWings(name, wing1, wing2);
this.className = this.body.className;
this.formation = 0;
this.pos = this.index + 1;
this.autoUpdate();
},
/**
* creates the body structure as dom branch of the flything
* @param name
* @returns {HTMLElement}
*/
createBody: function (name) {
//the actual structure of the body
var body = document.createElement("div");
body.className = "sprite " + name;
body.innerHTML = '<div class="bird">' +
'<div class="body">' +
'<div class="wing left">' +
'</div>' +
'<div class="wing right">' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
return body;
},
/**
* apply bas64 encoded image of wings to the left and right dom nodes
* representing the left and right wings
* @param name
* @param wing1
* @param wing2
*/
buildWings: function (name, wing1, wing2) {
var leftWing,
rightWing;
leftWing = this.domNode.querySelector(".sprite." + name + " .wing.left");
leftWing.style.background = "transparent url(" + wing1 + ") 0 0 no-repeat";
this.leftWing = leftWing;
rightWing = this.domNode.querySelector(".sprite." + name + " .wing.right");
rightWing.style.background = "transparent url(" + wing2 + ") 0 0 no-repeat";
this.rightWing = rightWing;
},
autoUpdate: function () {
this.body.className = this.className + " pos" + this.pos;
},
setFormation: function (index) {
this.formation = index;
this.updateCssClasses();
},
updateCssClasses: function () {
this.body.className = this.className + " pos" + this.pos + " formation" + this.formation;
},
destroy: function () {
window.clearInterval(this.updateTrigger);
//this.domNode.removeChild(this.body);
//this.domNode.removeChild()
}
};
var toggleClass = function (node, className) {
var r = new RegExp("(\b" + className + "\b)", "ig");
var matches = node.className.match(r);
if (matches && matches.length > 0) {
node.className.replace(r, "");
}
else {
node.className = node.className + " " + className;
}
console.log("mathces: " + matches);
};
var fill = function (ctx, w, h, bgColor) {
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, w, h);
};
var createGrid2 = function () {
var footprint = "00000000000000" +
"XXX00000000000" +
"XXX00000000000" +
"XXXX0000000000" +
"XXXXX000000000" +
"XXXXXX00000000" +
"XXXXXXX0000000" +
"XXXXXX00000000" +
"XXXXX000000000" +
"XXXXXX00000000" +
"XXXXXXX0000000" +
"XXXXXXXX000000" +
"XXXXXXXXX00000" +
"XXXXXXXXXX0000" +
"XXXXXXXXXXXXX0" +
"XXXXXXXXXXXXXX" +
"XXXXXXXXXXXX00" +
"XXXXXXX000XX00" +
"XXXXXX00000X00" +
"XXXXXXX0000000" +
"XXXX00XX000000" +
"XXX0000X000000" +
"XXXX0000000000" +
"XXXXX000000000" +
"00000000000000";
var w = 14;
var h = 25;
var p = 0;
var fp;
for (var row = 0; row < h; row++) {
for (var col = 0; col < w; col++) {
p = row * w + col;
fp = footprint[p];
if (fp === "X") {
var point = {
x: Math.round(col) + 1, //the horizontal baseline is at x = 0
y: row
};
grid.push(point);
console.log(p + "[" + row + "][" + col + "]: " + JSON.stringify(point));
}
}
}
console.log("Grid: ", grid);
};
var loadImage = function (url, callback) {
var img = new Image();
img.onload = function () {
if (callback) {
callback(img);
}
};
img.src = url;
return img;
};
var createBackground = function (terrain) {
var c = document.createElement('canvas');
var h = document.querySelector('.hidden');
//h.appendChild(c);
//terrain.addChild(h);
var ctx = c.getContext('2d');
var tileSize = 100;
var nTiles = 6;
c.width = nTiles * tileSize;
c.height = nTiles * tileSize;
var bgColors = "rgba(12,12,12,1);rgba(14,14,14,1);rgba(18,18,18,1);rgba(6,6,6,1)".split(";");
for (var row = 0; row < nTiles; row++) {
for (var col = 0; col < nTiles; col++) {
ctx.fillStyle = bgColors[Math.round(Math.random() * (bgColors.length - 1))];
var x = col * tileSize;
var y = row * tileSize;
ctx.fillRect(x, y, tileSize, tileSize);
}
}
var base64Image = c.toDataURL();
terrain.style.background = "transparent url(" + base64Image + ") 0 0 repeat";
};
var createGrid = function (ctx) {
var imageData = ctx.getImageData(0, 0, 14, 25);
var data = imageData.data;
var w = imageData.width;
var pw = w * 4;
var h = imageData.height;
var p = 0;
var pixel;
var str = "";
for (var row = 0; row < h; row++, p = row * pw) {
str = "";
for (var col = 0; col < pw; col += 4, p += 4) {
pixel = readPixel(data, p);
if (pixel.a === 0) {
str += "0";
}
else {
str += "X";
var point = {
x: Math.round(col / 4) + 1, //the horizontal baseline is at x = 0
y: row
};
grid.push(point);
//console.log("p: " + JSON.stringify(point));
}
}
console.log(str);
}
};
var readPixel = function (data, p) {
var r = data[p];
var g = data[p + 1];
var b = data[p + 2];
var a = data[p + 3];
return {
r: r,
g: g,
b: b,
a: a
};
};
//api
return {
setup: setup
};
};
window.flythings = flythings;
})(window);
flythings().setup();
@import "compass";
@mixin animate($val) {
-webkit-animation: $val;
-moz-animation: $val;
-ms-animation: $val;
-o-animation: $val;
animation: $val;
}
@mixin animate-delay($val) {
-webkit-animation-delay: $val;
-moz-animation-delay: $val;
-ms-animation-delay: $val;
-o-animation-delay: $val;
animation-delay: $val;
}
@mixin animate-timing-function ($val) {
-webkit-animation-timing-function: $val;
-moz-animation-timing-function: $val;
-ms-animation-timing-function: $val;
-o-animation-timing-function: $val;
animation-timing-function: $val;
}
@mixin transform-origin-2 ($val) {
-webkit-transform-origin: $val;
-moz-transform-origin: $val;
-ms-transform-origin: $val;
-o-transform-origin: $val;
transform-origin: $val;
}
$foldWidth: 400px;
html,
body {
height: 100%;
}
div, ul, li {
box-sizing: border-box;
padding: 0;
margin: 0;
}
$headingColor: #ffc628;
$grad: 5%;
h1.heading {
width: 100%;
height: 6rem;
line-height: 6rem;
color: $headingColor;
font-family: sans-serif;
font-size: 6rem;
font-weight: bold;
width: 100%;
padding: 0 1rem;
margin: 1rem auto;
text-align: center;
text-transform: uppercase;
text-shadow: 0 1px 0 darken($headingColor, 1 * $grad), 0 2px 0 darken($headingColor, 2 * $grad), 0 3px 0 darken($headingColor, 3 * $grad), 0 4px 0 darken($headingColor, 4 * $grad), 0 5px 0 #aaaaaa, 0 6px 1px rgba(0, 0, 0, 0.1), 0 0 5px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.3), 0 3px 5px rgba(0, 0, 0, 0.2), 0 5px 10px rgba(0, 0, 0, 0.25), 0 20px 20px rgba(0, 0, 0, 0.9);
}
p {
font-family: sans-serif;
font-size: 1rem;
color: darken(magenta, 25%);
text-align: center;
}
.terrain {
position: relative;
bottom: 0px;
left: 0px;
right: 0px;
width: 100%;
height: 100%;
background: transparent url(img/sky.png) 0 0 repeat;
@include animate(scroll 3s linear infinite);
}
.birds {
overflow: hidden;
}
.sprite {
position: absolute;
top: 0px;
left: 0px;
@include transform(scale(.25, .25) rotate3d(0,0,0,0));
@include transition(all 3s ease);
.bird {
position: absolute;
width: 500px;
height: 334px;
@include transform-origin(center center);
@include transform-style(preserve-3d);
@include animate(sine-x 7.11s linear infinite);
.body {
position: absolute;
width: 100%;
height: 100%;
@include transform-origin-2(50% 50%);
@include transform-style(preserve-3d);
@include animate(sine-y 9.33s linear infinite);
@include transition(all 3s ease-in-out);
.wing {
position: absolute;
height: 100%;
width: $foldWidth;
&.left {
background-position: (0 * -$foldWidth) 0px;
@include transform(translate3d(0, 0, 1px) rotate3d(0, 1, 0, 25deg));
@include transform-origin-2(right center);
@include animate(flutter 5s linear infinite);
@include animate-delay(-5s);
}
&.right {
left: $foldWidth;
background-position: (0 * -$foldWidth) 0px;
@include transform(translate3d(0, 0, 1px) rotate3d(0, 1, 0, -25deg));
@include transform-origin-2(left center);
@include animate(flutter 5s linear infinite);
@include animate-delay(-2.5s);
}
}
}
}
}
@mixin formation($index, $topPos,$leftPos) {
.formation#{$index} {
@for $i from 1 through length($topPos) {
&.pos#{$i} {
top: nth($topPos, $i);
left: nth($leftPos, $i);
}
}
}
}
$topPos0: 30%,40%,40%,50%,50%,60%,60%,70%,70%;
$leftPos0: 50%,40%,60%,30%,70%,20%,80%,10%,90%;
@include formation(0,$topPos0,$leftPos0);
$topPos1: 40%,40%,40%,40%,40%,40%,40%,40%,40%;
$leftPos1: 50%,40%,60%,30%,70%,20%,80%,10%,90%;
@include formation(1,$topPos1,$leftPos1);
$topPos2: 30%,30%,30%,60%,60%,60%,60%,60%,60%;
$leftPos2: 50%,40%,60%,30%,70%,20%,80%,10%,90%;
@include formation(2,$topPos2,$leftPos2);
$topPos3: 20%,30%,40%,50%,50%,50%,60%,60%,70%;
$leftPos3: 50%,50%,50%,50%,60%,40%,30%,70%,50%;
@include formation(3,$topPos3,$leftPos3);
$topPos4: 20%,20%,40%,40%,60%,60%,70%,70%,70%;
$leftPos4: 45%,55%,45%,55%,60%,40%,30%,70%,50%;
@include formation(4,$topPos4,$leftPos4);
$topPos4: 40%,40%,40%,40%,40%,40%,40%,40%,40%;
$leftPos4: 50%,50%,50%,50%,50%,50%,50%,50%,50%;
@include formation(4,$topPos4,$leftPos4);
$topPos5: 20%,20%,20%,30%,40%,50%,30%,40%,50%;
$leftPos5: 50%,40%,60%,40%,40%,40%,60%,60%,60%;
@include formation(5,$topPos5,$leftPos5);
#canvasHolder {
position: absolute;
top: 0px;
left: 0px;
width: 400px;
height: 400px;
background-color: white;
}
.regenerate {
position: absolute;
z-index: 100;
bottom: 10%;
left: 50%;
margin-left: -100px;
font-family: Arial;
border-radius: 5px 5px;
background-color: darkred;
width: 200px;
line-height: 3rem;
color: white;
text-transform: uppercase;
text-align: center;
cursor: pointer;
&:hover {
background-color: lighten(darkred, 15%);
}
}
@-webkit-keyframes sine-x {
0% {
@include transform (translate3d(0px, 0px, 0px) rotate3d(1,0,0,-5deg));
@include animate-timing-function(ease-out);
}
33% {
@include transform (translate3d(200px, 0px, 200px) rotate3d(1,0,0,20deg));
@include animate-timing-function(ease-in-out);
}
66% {
@include transform (translate3d(-200px, 0px, 800px) rotate3d(0,1,0,-10deg));
@include animate-timing-function(ease-in);
}
100% {
@include transform (translate3d(0px, 0px, 0px) rotate3d(0,1,0,-5deg));
@include animate-timing-function(ease-out);
}
}
@-webkit-keyframes sine-y {
0% {
@include transform (translate3d(0px, 200px, 0px));
@include animate-timing-function(ease-out);
}
25% {
@include transform (translate3d(0px, 400px, 200px));
@include animate-timing-function(ease-in-out);
}
75% {
@include transform (translate3d(0px, 0px, -300px));
@include animate-timing-function(ease-in);
}
100% {
@include transform (translate3d(0px, 200px, 0px));
@include animate-timing-function(ease-out);
}
}
@-webkit-keyframes flutter {
0% {
@include transform (rotate3d(0, 1, 0, 0deg));
@include animate-timing-function(ease-out);
}
25% {
@include transform (rotate3d(0, 1, 0, 65deg));
@include animate-timing-function(ease-in-out);
}
75% {
@include transform (rotate3d(0, 1, 0, -65deg));
@include animate-timing-function(ease-in);
}
100% {
@include transform (rotate3d(0, 1, 0, 0deg));
@include animate-timing-function(ease-out);
}
}
@-webkit-keyframes scroll {
from {background-position: 0 -500%}
to {background-position: 0 500%}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment