A Pen by Andy Andreas Hulstkamp on CodePen.
Last active
December 28, 2015 03:49
-
-
Save ndhu/7437850 to your computer and use it in GitHub Desktop.
A Pen by Andy Andreas Hulstkamp.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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