aka - "how the sausage is made"
Using NodeJS and Node Package Manager I install tools that run from the command line. One of the most important tools is GulpJS. Gulp allows me to hook a bunch of tools together to create a workflow that watches my source folders for changes, compresses, links, and cleans my source code into deployable files in the background. When files change (I edit and save some values in Javascript, for example) my folders are refreshed and I can see the result in the browser in realtime.
Here's a typical workspace, both annotated and clean.
I use Photoshop to create individual 24bpp PNG files with transparency. They are placed in a smart folder so I can update things quickly. I use a Mac app called TexturePacker to process the folder - it combines the images into a single spritesheet and a CSS file which tells my code where to find the image. In the included example it turns ~215kb of image data into 48kb of image and CSS. This has the added advantage of requiring 2 HTTP calls for 17 images - that's a big savings.
I use GreenSock to give me code-driven animation. It's a wonderful tool. I leaned on it heavily when I was doing Bridgestone banners in Flash at Laughlin/Constable in ~2008 - funny old world... Since the collapse of Flash GreenSock's ported the suite of tools to Javascript - it's still excellent.
You define the object you want to move in the DOM, by ID, and move it based on time, tween, property, easing, and delay. I can timeline a whole banner in ~ a dozen lines of code.
tl.from(tb0, 0.66, { left: 0, opacity: 0.0, ease: easing }, t + 0.25);
tl.from(tb1, 0.66, { left: 300, opacity: 0.0, ease: easing }, t + 0.5);
tl.to(tb0, 2.5, { left: 100 }, t + 1.00);
tl.to(tb1, 2.5, { left: 190 }, t + 1.00);
tl.to(clouds_mid, 10, { x: 20 }, 0);
tl.to(clouds_back, 10, { x: 50 }, 0);I use a text editor to create the HTML, Javascript, and LESS (CSS) files.
You can open any of the .js, .less, .css, .html files to see what's inside. Compare the _style.less and the _style.css files or the _app.js from the source and compiled folders, and you'll see how the code is optimized and minified for deployment.
All the banner code is condensed, whitespace removed, optimized and, wherever possible, obfuscated.
Below is an example of the randomized lightning for the particle system we wrote for the Blizzak banners. I've put the source and the compiled code side-by-side.
/*---------------------------------------*/
// falling snow
/*---------------------------------------*/
var flurry = (function() {
var flakes = [];
var maxParticles = 50;
var wind = -.1;
var gravity = .05;
var targetFPS = 30;
var tick;
function init(element, fps, maxP, wnd, grav) {
targetFPS = fps
maxParticles = maxP;
wind = wnd;
gravity = grav;
buildSnow();
for (var f in flakes) {
resetFlake(flakes[f]);
}
tick = setInterval(function() {
stepFrame();
}, 1000 / targetFPS);
}
function resetFlake(data) {
var w = stage.offsetWidth;
var flake = document.getElementById(data.id);
if(data.killBit){
return;
}
data.y = -(Math.random() * 100) - 50;
flake.style.top = data.y + 'px';
data.x = (Math.random() * w);
flake.style.left = data.x + 'px';
data.rx = (Math.random() * 100) * wind;
data.ry = ((Math.random() * 100) * gravity) + 0.3;
var w = flake.style.height = (Math.random() * 10) + 2;
flake.style.width = w + 'px';
flake.style.webkitTransform = 'rotate(' + Math.random() * 180 + 'deg)';
flake.style.opacity = .9;
if (w > 30) {
flake.style.opacity = .33;
}
if (w > 20) {
flake.style.opacity = .66;
}
flake.style.opacity = 1.0;
}
function stepFrame() {
var data;
var flake;
for (var f in flakes) {
data = flakes[f];
flake = document.getElementById(data.id);
var o = flake.style.opacity;
if(data.killBit && Math.random() * 10 > 8){
o -= .03;
}
if(o > 0.0){
// change the data
data.y += data.ry;
data.x += data.rx;
// reflect in particle
flake.style.top = data.y + 'px';
flake.style.left = data.x + 'px';
flake.style.opacity = o;
}
if (data.y > parseInt(window.getComputedStyle(stage).height, 10)) {
resetFlake(data);
}
if (data.x > parseInt(window.getComputedStyle(stage).width, 10)) {
resetFlake(data);
}
if (data.x < 0 && !data.killBit) {
resetFlake(data);
}
}
}
function kill(){
for (var f in flakes) {
flakes[f].killBit = true;
}
};
function killAll(){
for (var f in flakes) {
var data = flakes[f];
var flake = document.getElementById(data.id);
flake.style.left = -1000;
flake.style.top = -1000;
}
flakes = [];
clearInterval(tick);
}
function addFlake(i) {
cln = snow.cloneNode(true);
cln.id = 'flake_' + i;
stage.appendChild(cln);
flakes.push({
id: 'flake_' + i,
x: 0,
y: 0,
rx: 0,
ry: 0,
killBit: false,
});
}
function buildSnow() {
for (var i = 0; i < maxParticles; i++) {
addFlake(i);
}
}
return {
init: init,
kill:kill,
killAll:killAll,
};
})();
var flurry=function(){var o,r=[],y=50,d=-.1,s=.05,f=30;function c(t){var e=stage.offsetWidth,l=document.getElementById(t.id);if(!t.killBit){t.y=-100*Math.random()-50,l.style.top=t.y+"px",t.x=Math.random()*e,l.style.left=t.x+"px",t.rx=100*Math.random()*d,t.ry=100*Math.random()*s+.3;e=l.style.height=10*Math.random()+2;l.style.width=e+"px",l.style.webkitTransform="rotate("+180*Math.random()+"deg)",l.style.opacity=.9,30parseInt(window.getComputedStyle(stage).height,10)&&c(t),t.x>parseInt(window.getComputedStyle(stage).width,10)&&c(t),t.x<0&&!t.killBit&&c(t)}}()},1e3/f)},kill:function(){for(var t in r)r[t].killBit=!0},killAll:function(){for(var t in r){var e=r[t],l=document.getElementById(e.id);l.style.left=-1e3,l.style.top=-1e3}r=[],clearInterval(o)}}}();
Jan 26, 2019