-
Notifications
You must be signed in to change notification settings - Fork 0
/
starfield.js
267 lines (238 loc) · 8.51 KB
/
starfield.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/** Copied from http://jsfiddle.net/ditman/8Ffrw/ */
/**
* The stars in our starfield!
* Stars coordinate system is relative to the CENTER of the canvas
* @param {number} x
* @param {number} y
*/
var Star = function(x, y, maxSpeed) {
this.x = x;
this.y = y;
this.slope = y / x; // This only works because our origin is always (0,0)
this.opacity = 0;
this.speed = Math.max(Math.random() * maxSpeed, 1);
};
/**
* Compute the distance of this star relative to any other point in space.
*
* @param {int} originX
* @param {int} originY
*
* @return {float} The distance of this star to the given origin
*/
Star.prototype.distanceTo = function(originX, originY) {
return Math.sqrt(Math.pow(originX - this.x, 2) + Math.pow(originY - this.y, 2));
};
/**
* Reinitializes this star's attributes, without re-creating it
*
* @param {number} x
* @param {number} y
*
* @return {Star} this star
*/
Star.prototype.resetPosition = function(x, y, maxSpeed) {
Star.apply(this, arguments);
return this;
};
/**
* The BigBang factory creates stars (Should be called StarFactory, but that is
* a WAY LESS COOL NAME!
* @type {Object}
*/
var BigBang = {
/**
* Returns a random star within a region of the space.
*
* @param {number} minX minimum X coordinate of the region
* @param {number} minY minimum Y coordinate of the region
* @param {number} maxX maximum X coordinate of the region
* @param {number} maxY maximum Y coordinate of the region
*
* @return {Star} The random star
*/
getRandomStar: function(minX, minY, maxX, maxY, maxSpeed) {
var coords = BigBang.getRandomPosition(minX, minY, maxX, maxY);
return new Star(coords.x, coords.y, maxSpeed);
},
/**
* Gets a random (x,y) position within a bounding box
*
*
* @param {number} minX minimum X coordinate of the region
* @param {number} minY minimum Y coordinate of the region
* @param {number} maxX maximum X coordinate of the region
* @param {number} maxY maximum Y coordinate of the region
*
* @return {Object} An object with random {x, y} positions
*/
getRandomPosition: function(minX, minY, maxX, maxY) {
return {
x: Math.floor((Math.random() * maxX) + minX),
y: Math.floor((Math.random() * maxY) + minY)
};
}
};
/**
* Constructor function of our starfield. This just prepares the DOM nodes where
* the scene will be rendered.
*
* @param {string} canvasId The DOM Id of the <div> containing a <canvas> tag
*/
var StarField = function(containerId) {
this.container = document.getElementById(containerId);
this.canvasElem = this.container.getElementsByTagName('canvas')[0];
this.canvas = this.canvasElem.getContext('2d');
this.width = this.container.offsetWidth;
this.height = this.container.offsetHeight;
this.starField = [];
};
/**
* Updates the properties for every star for the next frame to be rendered
*/
StarField.prototype._updateStarField = function() {
var i,
star,
randomLoc,
increment;
for (i = 0; i < this.numStars; i++) {
star = this.starField[i];
increment = Math.min(star.speed, Math.abs(star.speed / star.slope));
star.x += (star.x > 0) ? increment : -increment;
star.y = star.slope * star.x;
star.opacity += star.speed / 100;
// Recycle star obj if it goes out of the frame
if ((Math.abs(star.x) > this.width / 2) ||
(Math.abs(star.y) > this.height / 2)) {
//randomLoc = BigBang.getRandomPosition(
// -this.width / 2, -this.height / 2,
// this.width, this.height
//);
randomLoc = BigBang.getRandomPosition(
-this.width / 10, -this.height / 10,
this.width / 5, this.height / 5
);
star.resetPosition(randomLoc.x, randomLoc.y, this.maxStarSpeed);
}
}
};
/**
* Renders the whole starfield (background + stars)
* This method could be made more efficient by just blipping each star,
* and not redrawing the whole frame
*/
StarField.prototype._renderStarField = function() {
var i,
star;
// Background
this.canvas.fillStyle = "rgba(0, 0, 0, .5)";
this.canvas.fillRect(0, 0, this.width, this.height);
// Stars
for (i = 0; i < this.numStars; i++) {
star = this.starField[i];
this.canvas.fillStyle = "rgba(255, 255, 255, " + star.opacity + ")";
this.canvas.fillRect(
star.x + this.width / 2,
star.y + this.height / 2,
2, 2);
}
};
/**
* Function that handles the animation of each frame. Update the starfield
* positions and re-render
*/
StarField.prototype._renderFrame = function(elapsedTime) {
var timeSinceLastFrame = elapsedTime - (this.prevFrameTime || 0);
window.requestAnimationFrame(this._renderFrame.bind(this));
// Skip frames unless at least 30ms have passed since the last one
// (Cap to ~30fps)
if (timeSinceLastFrame >= 30 || !this.prevFrameTime) {
this.prevFrameTime = elapsedTime;
this._updateStarField();
this._renderStarField();
}
};
/**
* Makes sure that the canvas size fits the size of its container
*/
StarField.prototype._adjustCanvasSize = function(width, height) {
// Set the canvas size to match the container ID (and cache values)
this.width = this.canvasElem.width = width || this.container.offsetWidth;
this.height = this.canvasElem.height = height || this.container.offsetHeight;
};
/**
* This listener compares the old container size with the new one, and caches
* the new values.
*/
StarField.prototype._watchCanvasSize = function(elapsedTime) {
var timeSinceLastCheck = elapsedTime - (this.prevCheckTime || 0),
width,
height;
window.requestAnimationFrame(this._watchCanvasSize.bind(this));
// Skip frames unless at least 333ms have passed since the last check
// (Cap to ~3fps)
if (timeSinceLastCheck >= 333 || !this.prevCheckTime) {
this.prevCheckTime = elapsedTime;
width = this.container.offsetWidth;
height = this.container.offsetHeight;
if (this.oldWidth !== width || this.oldHeight !== height) {
this.oldWidth = width;
this.oldHeight = height;
this._adjustCanvasSize(width, height);
}
}
};
/**
* Initializes the scene by resizing the canvas to the appropiate value, and
* sets up the main loop.
* @param {int} numStars Number of stars in our starfield
*/
StarField.prototype._initScene = function(numStars) {
var i;
for (i = 0; i < this.numStars; i++) {
this.starField.push(
BigBang.getRandomStar(-this.width / 2, -this.height / 2, this.width, this.height, this.maxStarSpeed)
);
}
// Intervals not stored because I don't plan to detach them later...
window.requestAnimationFrame(this._renderFrame.bind(this));
window.requestAnimationFrame(this._watchCanvasSize.bind(this));
};
/**
* Kicks off everything!
* @param {int} numStars The number of stars to render
* @param {int} maxStarSpeed Maximum speed of the stars (pixels / frame)
*/
StarField.prototype.render = function(numStars, maxStarSpeed) {
this.numStars = numStars || 100;
this.maxStarSpeed = maxStarSpeed || 3;
this._initScene(this.numStars);
};
/**
* requestAnimationFrame shim layer with setTimeout fallback
* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating
*/
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
// Kick off!
var starField = new StarField('fullScreen').render(333, 3);