Skip to content

Commit 9a7aaf1

Browse files
committed
Lots new stuff
1 parent 9782096 commit 9a7aaf1

File tree

125 files changed

+14984
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

125 files changed

+14984
-1
lines changed

public/bean_photos/bean.js

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
/**
2+
* @class Bean
3+
* Bean is a silly thing which uses Canvas to drop images onto the screen in
4+
* a stack - imagine dropping a bunch of photographs onto a table and you'll
5+
* understand what Bean does. Why is it called Bean though? I once knew a man named Bean.
6+
*/
7+
Bean = function(config) {
8+
config = config || {};
9+
10+
var defaults = {
11+
/**
12+
* @property imageUrls
13+
* @type Array
14+
* The array of image urls to use as photographs
15+
*/
16+
imageUrls: [],
17+
18+
/**
19+
* @property canvasId
20+
* @type String
21+
* The DOM id of the Canvas to use (required)
22+
*/
23+
canvasId: '',
24+
25+
/**
26+
* @property loopSlides
27+
* @type boolean
28+
* False to prevent repeat cycle of images
29+
*/
30+
loopSlides: false,
31+
32+
/**
33+
* @property randomize
34+
* @type Boolean
35+
* True to drop photos in random order (defaults to true). Otherwise they are dropped
36+
* in the order they are defined in the images array
37+
*/
38+
randomize: false,
39+
40+
/**
41+
* @property interval
42+
* @type Number
43+
* Number of milliseconds between each drop (defaults to 5000)
44+
*/
45+
interval: 4000,
46+
47+
/**
48+
* @property fallDuration
49+
* @type Number
50+
* Number of milliseconds it takes for each image to fall (defaults to 3000)
51+
*/
52+
fallDuration: 3000,
53+
54+
/**
55+
* @property backgroundColor
56+
* @type String
57+
* The hex color code to use for the canvas background (defaults to black - "#000000")
58+
*/
59+
backgroundColor: "#000000",
60+
61+
/**
62+
* @property constrain
63+
* @type Boolean
64+
* True to ensure that the each image falls within the bounds of the canvas
65+
*/
66+
constrain: true,
67+
68+
/**
69+
* @property fillBody
70+
* @type Boolean
71+
* True to resize the canvas to the full size of the window (defaults to false)
72+
*/
73+
fillBody: false,
74+
75+
/**
76+
* @property useKeyFrames
77+
* @type Boolean
78+
* True to optimize animation by using key frames. This takes a snapshot of all
79+
* Plungers that have already landed each time one lands, and then only redraws
80+
* the moving Plungers. Defaults to true. Does not work with images loaded from
81+
* another domain.
82+
*/
83+
useKeyFrames: true
84+
};
85+
86+
//apply defaults and config
87+
for (var key in defaults) {
88+
this[key] = config[key] || defaults[key];
89+
}
90+
91+
//turn off key frames if any images are from another domain
92+
//FIXME: this could identify local urls as cross-domain if they are fully specified
93+
for (var i=0, j = config.imageUrls.length; i < j; i++) {
94+
if (/^http/.test(config.imageUrls[i])) this.useKeyFrames = false;
95+
}
96+
97+
/**
98+
* @property images
99+
* @type Array
100+
* The array of Image objects which are preloaded using the imageUrls config
101+
*/
102+
this.images = [];
103+
104+
/**
105+
* @property plungers
106+
* @type Array
107+
* The set of plunging objects. These are each drawn on every iteration
108+
*/
109+
this.plungers = new Bean.Plungers();
110+
111+
/**
112+
* @property initialized
113+
* @type Boolean
114+
* True when Bean has been initialized and all images preloaded
115+
*/
116+
this.initialized = false;
117+
118+
/**
119+
* @property onReadyCallbacks
120+
* @type Array
121+
* The array of callback functions to call when all images have been loaded
122+
*/
123+
this.onReadyCallbacks = [];
124+
125+
/**
126+
* @property lastPlungerAdded
127+
* @type Date
128+
* The time the last plunger was added
129+
*/
130+
this.lastPlungerAdded = new Date(new Date() - this.interval);
131+
132+
this.initialize();
133+
};
134+
135+
Bean.prototype = {
136+
137+
/**
138+
* Sets up the canvas element
139+
*/
140+
initialize: function() {
141+
/**
142+
* @property canvas
143+
* @type HTMLElement
144+
* The Canvas element (NOT the context - see this.context)
145+
*/
146+
this.canvas = document.getElementById(this.canvasId);
147+
148+
if (this.fillBody) {
149+
var body = document.body;
150+
151+
this.canvas.width = body.clientWidth;
152+
this.canvas.height = body.clientHeight;
153+
}
154+
155+
/**
156+
* @property context
157+
* @type Context2d
158+
* The 2d canvas context
159+
*/
160+
this.context = this.canvas.getContext('2d');
161+
162+
if (this.useKeyFrames) {
163+
/**
164+
* @property takeKeyFrame
165+
* @type Boolean
166+
* @private
167+
* True if a keyframe should be generated next time the scene is drawn.
168+
* This is usually set to true after a Plunger has landed so that it can
169+
* be pruned from being re-rendered on every frame
170+
*/
171+
this.takeKeyFrame = false;
172+
173+
/**
174+
* @property currentKeyFrame
175+
* @type Image
176+
* The current keyframe image.
177+
*/
178+
this.currentKeyFrame = undefined;
179+
180+
this.plungers.onPlungeComplete(function() {
181+
this.takeKeyFrame = true;
182+
}, this);
183+
}
184+
185+
this.preloadImages();
186+
},
187+
188+
/**
189+
* Iterates over this.imageUrls and preloads all images
190+
*/
191+
preloadImages: function() {
192+
var urls = this.imageUrls,
193+
total = this.imageUrls.length,
194+
loaded = 0;
195+
196+
//used in the loadCallback below
197+
var onReadyCallbacks = this.onReadyCallbacks;
198+
var me = this;
199+
200+
//Returns a function which is called after each image loads.
201+
//Calls each onReady callback when the final image has been loaded
202+
var loadCallback = function(image, id) {
203+
return function() {
204+
loaded += 1;
205+
me.images[id] = image;
206+
207+
if (loaded == total) {
208+
for (var i=0; i < onReadyCallbacks.length; i++) {
209+
var cfg = onReadyCallbacks[i];
210+
211+
cfg.fn.call(cfg.scope, me);
212+
}
213+
}
214+
};
215+
};
216+
217+
//load the actual images
218+
for (var i=0, j = urls.length; i < j; i++) {
219+
var image = new Image();
220+
image.onload = loadCallback(image, i);
221+
image.src = urls[i];
222+
}
223+
},
224+
225+
/**
226+
* Registers a function to be called when all images have been loaded.
227+
* The function will be called with a single argument - this Bean instance.
228+
* @param {Function} fn The function to call
229+
* @param {Object} scope Optional scope to call the function with (defaults to this)
230+
*/
231+
onReady: function(fn, scope) {
232+
this.onReadyCallbacks.push({
233+
fn : fn,
234+
scope: scope || this
235+
});
236+
},
237+
238+
/**
239+
* Draws the frame with all existing and currently falling images
240+
*/
241+
drawFrame: function() {
242+
var frameTime = new Date() - this.lastFrameTime,
243+
totalTime = new Date() - this.startTime,
244+
context = this.context,
245+
plungers = this.plungers;
246+
247+
//create a new plunger if required
248+
if (new Date() - this.lastPlungerAdded > this.interval) {
249+
(this.loopSlides || (this.counter++ < this.imageUrls.length)) ? this.addPlunger() : this.stop();
250+
}
251+
252+
//take a key frame if necessary
253+
if (this.takeKeyFrame) {
254+
this.currentKeyFrame = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
255+
256+
//we've now taken the keyframe, no need to take another next frame
257+
this.takeKeyFrame = false;
258+
}
259+
260+
//clear canvas and redraw everything
261+
this.clearCanvas();
262+
var moving = plungers.getMoving();
263+
264+
for (var i=0, j = moving.length; i < j; i++) {
265+
this.withContext(function(context) {
266+
moving[i].draw(context);
267+
});
268+
}
269+
270+
this.lastFrameTime = new Date();
271+
},
272+
273+
/**
274+
* Starts dropping the photos
275+
*/
276+
start: function() {
277+
/**
278+
* @property startTime
279+
* @type Date
280+
* The time the animation started. Used for calculating when to drop next plunger, etc
281+
*/
282+
this.startTime = new Date();
283+
284+
/**
285+
* @property lastFrameTime
286+
* @type Date
287+
* The time the last frame was drawn
288+
*/
289+
this.lastFrameTime = new Date();
290+
this.counter = 0;
291+
292+
var me = this;
293+
294+
var looper = function() {
295+
me.drawFrame.call(me);
296+
};
297+
298+
this.clearCanvas(false);
299+
300+
this.running = setInterval(looper,100);
301+
},
302+
303+
/**
304+
* Stop dropping the photos. Maybe.
305+
*/
306+
stop: function() {
307+
clearInterval(this.running);
308+
},
309+
310+
/**
311+
* Adds a new plunger with randomised location and end rotation
312+
*/
313+
addPlunger: function(config) {
314+
config = config || {};
315+
316+
var imageId = 0;
317+
if (this.randomize) {
318+
imageId = Math.floor(Math.random() * this.images.length);
319+
} else {
320+
this.lastImageId = this.lastImageId || 0;
321+
this.lastImageId ++;
322+
this.lastImageId = this.lastImageId % this.images.length;
323+
imageId = this.lastImageId;
324+
}
325+
326+
var defaults = {
327+
fallDuration: this.fallDuration,
328+
image : this.images[imageId],
329+
endRotation : (Math.random() * Math.PI / 2) - (Math.PI / 4),
330+
xPos : Math.random() * (this.canvas.width - 100) + 50,
331+
yPos : Math.random() * (this.canvas.height - 100) + 50
332+
};
333+
334+
for (var key in config) {
335+
defaults[key] = config[key];
336+
}
337+
338+
this.plungers.add(new Bean.Plunger(defaults));
339+
340+
this.lastPlungerAdded = new Date();
341+
},
342+
343+
/**
344+
* Clears the canvas by filling it with the backgroundColor
345+
* @param {Boolean} redrawKeyFrame True to automatically redraw the background keyframe
346+
* immediately after clearing (defaults to true)
347+
*/
348+
clearCanvas: function(redrawKeyFrame) {
349+
this.withContext(function(context) {
350+
if (redrawKeyFrame !== false && this.currentKeyFrame != undefined) {
351+
context.putImageData(this.currentKeyFrame, 0, 0, this.canvas.height, this.canvas.width);
352+
} else {
353+
context.fillStyle = this.backgroundColor;
354+
context.fillRect(0, 0, this.canvas.width, this.canvas.height);
355+
}
356+
});
357+
},
358+
359+
/**
360+
* Runs a function with the context as a single argument. Saves and restores the context
361+
* so as not to pollute other drawing functions
362+
* @param {Function} fn The function to run
363+
* @param {Object} scope Optional scope to call the function in (defaults to this)
364+
*/
365+
withContext: function(fn, scope) {
366+
var context = this.context;
367+
368+
context.save();
369+
fn.call(scope || this, context);
370+
context.restore();
371+
}
372+
};
Loading
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)