The version of vichan running on lainchan.org
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

617 linhas
18KB

  1. /*
  2. * fireworks.js: A JavaScript animation experiment
  3. * -----------------------------------------------
  4. * http://schillmania.com/projects/fireworks/
  5. *
  6. * Provided "as-is", free and without warranty
  7. * Originally written in 2005. Old code ahead.
  8. *
  9. * Includes SoundManager 2 API (BSD).
  10. * http://schillmania.com/projects/soundmanager2/
  11. *
  12. * v0.9.1.20110703
  13. */
  14. /*jslint white: false, onevar: false, undef: true, nomen: false, eqeqeq: false, plusplus: false, bitwise: true, regexp: false, newcap: true, immed: true */
  15. /*global window, document, navigator, setTimeout, setInterval, clearInterval, enableDebugMode, writeDebug, soundManager, FireworkParticle, attachEvent */
  16. var fc;
  17. function Animator() {
  18. var self = this;
  19. writeDebug('Animator()');
  20. this.tweens = [];
  21. this.tweens['default'] = [1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1];
  22. this.tweens.blast = [12,12,11,10,10,9,8,7,6,5,4,3,2,1];
  23. this.tweens.fade = [10,10,10,10,10,10,10,10,10,10];
  24. this.queue = [];
  25. this.queue.IDs = [];
  26. this.active = false;
  27. this.timer = null;
  28. this.createTween = function(start,end,type) {
  29. // return array of tween coordinate data (start->end)
  30. type = type||'default';
  31. var tween = [start];
  32. var tmp = start;
  33. var diff = end-start;
  34. var x = self.tweens[type].length;
  35. for (var i=0; i<x; i++) {
  36. tmp += diff*self.tweens[type][i]*0.01;
  37. tween[i] = {};
  38. tween[i].data = tmp;
  39. tween[i].event = null;
  40. }
  41. return tween;
  42. };
  43. this.enqueue = function(o,fMethod,fOnComplete) {
  44. // add object and associated methods to animation queue
  45. // writeDebug('animator.enqueue()');
  46. var i;
  47. if (!fMethod) {
  48. writeDebug('animator.enqueue(): missing fMethod');
  49. }
  50. if (typeof(self.queue.IDs[o.oID])=='undefined') {
  51. // writeDebug('animator.enqueue(): added '+o.oID);
  52. i = self.queue.length;
  53. self.queue.IDs[o.oID] = i;
  54. self.queue[i] = o;
  55. } else {
  56. // writeDebug('animator.enqueue(): '+o.oID+' already queued');
  57. i = self.queue.IDs[o.oID]; // retrieve queue index
  58. self.queue[i].active = true;
  59. self.queue[i].frame = 0;
  60. }
  61. o.active = true; // flag for animation
  62. self.queue[i]._method = fMethod;
  63. self.queue[i]._oncomplete = fOnComplete?fOnComplete:null;
  64. };
  65. this.animate = function() {
  66. var active = 0;
  67. for (var i=self.queue.length; i--;) {
  68. if (self.queue[i].active) {
  69. self.queue[i]._method();
  70. active++;
  71. }
  72. }
  73. if (active===0 && self.timer) {
  74. // all animations finished
  75. self.stop();
  76. } else {
  77. // writeDebug(active+' active');
  78. }
  79. };
  80. this.start = function() {
  81. if (self.timer || self.active) {
  82. // writeDebug('animator.start(): already active');
  83. return false;
  84. }
  85. // writeDebug('animator.start()'); // report only if started
  86. self.active = true;
  87. self.timer = setInterval(self.animate,fc.intervalRate);
  88. };
  89. this.stop = function() {
  90. // writeDebug('animator.stop()',true);
  91. clearInterval(self.timer);
  92. self.timer = null;
  93. self.active = false;
  94. self.queue = [];
  95. self.queue.IDs = [];
  96. };
  97. }
  98. function FireworksController() {
  99. var self = this;
  100. this.intervalRate = 20; // rate (ms) to run animation at, general best default = 20
  101. this.DEBUG = true; // debug mode disabled by default
  102. this.oFW = null;
  103. this.isIE = !!(navigator.userAgent.match(/msie/i));
  104. this.isOpera = !!(navigator.userAgent.match(/opera/i));
  105. if (this.isOpera) {
  106. this.isIE = false; // no impersonation allowed here!
  107. }
  108. this.fireworks = [];
  109. this.animator = null;
  110. this.gOID = 0; // global object ID counter (for animation queue)
  111. this.particleTypes = 6;
  112. this.particleXY = 10;
  113. this.tweenFade = [100,90,80,70,60,50,40,30,20,10,0];
  114. this.isSafari = !!(navigator.appVersion.match(/webkit/i));
  115. this.canvasX = null;
  116. this.canvasY = null;
  117. this.screenY = null; // screen area (not entire page)
  118. self.scrollY = null;
  119. self.getWindowCoords = function() {
  120. self.canvasX = (document.documentElement.clientWidth||document.body.clientWidth||document.body.scrollWidth);
  121. self.canvasY = (document.documentElement.clientHeight||document.body.clientHeight||document.body.scrollHeight);
  122. self.screenY = self.canvasY;
  123. self.scrollY = parseInt(window.scrollY||document.documentElement.scrollTop||document.body.scrollTop, 10);
  124. self.canvasY += self.scrollY;
  125. };
  126. this.getWindowCoordsAlt = function() {
  127. self.canvasX = window.innerWidth-16;
  128. self.canvasY = window.innerHeight;
  129. self.screenY = self.canvasY;
  130. self.scrollY = parseInt(window.scrollY||document.documentElement.scrollTop||document.body.scrollTop, 10);
  131. self.canvasY += self.scrollY;
  132. };
  133. this.getPanX = function(x) {
  134. x = parseInt(x, 10);
  135. var pos = x/self.canvasX;
  136. if (pos<0.4) {
  137. pos *= -1;
  138. } else if (pos >= 0.4 && pos <= 0.6) {
  139. pos = 0.5;
  140. }
  141. pos = parseInt(pos*100, 10);
  142. // writeDebug('getPanX('+x+'): '+pos+'%');
  143. return pos;
  144. };
  145. this.isEmpty = function(o) {
  146. // needs further hacking
  147. return (typeof(o)=='undefined'||(o===null&&o!==0)||(o===''&&o!==0)||o=='null');
  148. };
  149. this.init = function() {
  150. self.oFW = document.getElementById('fw');
  151. self.oFP = document.getElementById('fp');
  152. if (typeof(enableDebugMode)!='undefined' && (self.DEBUG||window.location.toString().toLowerCase().indexOf('debug')>=0)) {
  153. enableDebugMode();
  154. }
  155. self.getWindowCoords();
  156. self.animator = new Animator();
  157. };
  158. this.destructor = function() {
  159. for (var i=self.fireworks.length; i--;) {
  160. self.fireworks[i] = null;
  161. }
  162. self.fireworks = null;
  163. };
  164. if (this.isSafari || this.isOpera) {
  165. this.getWindowCoords = this.getWindowCoordsAlt;
  166. }
  167. }
  168. function Firework(oC,startX,startY,burstX,burstY,burstType,nRadius,nParticles,nCircles,allowRandom,obeyBoundaries) {
  169. var self = this;
  170. this.oID = 'fp'+(fc.gOID++); // may be unneeded
  171. var p = '';
  172. for (var i=0; i<arguments.length-1; i++) {
  173. p += arguments[i]+',';
  174. }
  175. p += arguments[i];
  176. writeDebug('firework('+p+')');
  177. this.oC = oC;
  178. this.o = fc.oFW.cloneNode(!fc.isIE?true:false);
  179. this.particles = [];
  180. this.vX = -1;
  181. this.vY = -4;
  182. this.x = startX;
  183. this.y = startY;
  184. this.allowRandom = allowRandom;
  185. this.obeyBoundaries = obeyBoundaries;
  186. this.frame = 0;
  187. this.tween = [];
  188. this.active = false;
  189. this.moveTo = function(x,y) {
  190. self.o.style.left = x+'px';
  191. self.o.style.top = y+'px';
  192. self.x = x;
  193. self.y = y;
  194. };
  195. this.slideTo = function(x,y) {
  196. self.tween = [fc.animator.createTween(self.x,x,'blast'),fc.animator.createTween(self.y,y,'blast')];
  197. fc.animator.enqueue(self,self.animate,self.aniExplode);
  198. };
  199. this.aniExplode = function() {
  200. // called from animation finish
  201. self.o.style.background = 'none';
  202. self.o.style.border = 'none';
  203. for (var i=self.particles.length; --i;) {
  204. self.particles[i].o.style.display = 'block';
  205. fc.animator.enqueue(self.particles[i],self.particles[i].animate);
  206. }
  207. // attach oncomplete event handler to last particle
  208. self.particles[i].o.style.display = 'block';
  209. fc.animator.enqueue(self.particles[i],self.particles[i].animate,self.beginFade);
  210. var sID = 'boom'+parseInt(Math.random()*8, 10);
  211. if (soundManager.ok()) {
  212. soundManager.play(sID, {pan: fc.getPanX(self.x)});
  213. }
  214. };
  215. this.beginFade = function() {
  216. // writeDebug('beginFade');
  217. self.tween = fc.animator.createTween(1,0,'fade');
  218. fc.animator.enqueue(self,self.aniFade,self.destructor);
  219. };
  220. this.aniFade = function() {
  221. // writeDebug('firework.aniFade('+self.tween[self.frame].data+')');
  222. for (var i=self.particles.length; i--;) {
  223. self.particles[i].moveRel();
  224. self.particles[i].nextState();
  225. self.particles[i].setOpacity(fc.tweenFade[self.frame]);
  226. }
  227. if (self.frame++>=self.tween.length) {
  228. self.active = false;
  229. self.frame = 0;
  230. if (self._oncomplete) {
  231. self._oncomplete();
  232. }
  233. self._oncomplete = null;
  234. return false;
  235. }
  236. return true;
  237. };
  238. this.destructor = function() {
  239. writeDebug('firework.destructor()');
  240. // for (var i=0; i<self.particles.length; i++) {
  241. for (var i=self.particles.length; i--;) {
  242. self.particles[i].destructor();
  243. self.particles[i] = null;
  244. }
  245. self.particles = null;
  246. self.oC.removeChild(self.o);
  247. self.o = null;
  248. self.oC = null;
  249. };
  250. this.animate = function() {
  251. // generic animation method
  252. self.moveTo(self.tween[0][self.frame].data,self.tween[1][self.frame].data,'burst');
  253. if (self.frame++>=self.tween[0].length-1) {
  254. self.active = false;
  255. self.frame = 0;
  256. if (self._oncomplete) {
  257. self._oncomplete();
  258. }
  259. self._oncomplete = null;
  260. return false;
  261. }
  262. return true;
  263. };
  264. this.createBurst = function(circles,nMax,rMax,type) {
  265. // c: # of circles, n: # of particles per circle, r: max radius
  266. writeDebug('firework.createBurst('+circles+','+nMax+','+rMax+','+type+')');
  267. var i=0, j=0;
  268. var tmp = 0;
  269. var radiusInc = rMax/circles;
  270. var radius = radiusInc;
  271. var angle = 0;
  272. var angleInc = 0; // per-loop increment
  273. var radiusOffset = (self.allowRandom?(0.33+Math.random()):1);
  274. var particlesPerCircle = [];
  275. var isRandom = Math.random()>0.5;
  276. var circleTypes = [type,circles>1?parseInt(Math.random()*fc.particleTypes, 10):type];
  277. var thisType = null;
  278. for (i=0; i<circles; i++) {
  279. particlesPerCircle[i] = parseInt(nMax*(i+1)/circles*1/circles, 10)||1; // hack - nMax*(i+1)/circles;
  280. angle = angleInc; // could be offset as well
  281. angleInc = 360/particlesPerCircle[i];
  282. thisType = circleTypes[i%2];
  283. for (j=0; j<particlesPerCircle[i]; j++) {
  284. self.particles[tmp] = new FireworkParticle(self.o,self.allowRandom,thisType,burstX,burstY,self.obeyBoundaries);
  285. self.particles[tmp].slideTo(radius*Math.cos(angle*Math.PI/180),radius*radiusOffset*Math.sin(angle*Math.PI/180));
  286. angle += angleInc;
  287. tmp++;
  288. }
  289. radius += radiusInc; // increase blast radius
  290. }
  291. };
  292. // startX,startY,burstX,burstY,burstType,nRadius,nParticles,nCircles
  293. self.oC.appendChild(self.o);
  294. self.moveTo(self.x,self.y);
  295. self.createBurst(nCircles,nParticles,nRadius,burstType); // create an explosion
  296. self.slideTo(burstX,burstY);
  297. var sID = 'fire'+parseInt(Math.random()*2, 10);
  298. if (soundManager.ok()) {
  299. soundManager.play(sID, {pan: fc.getPanX(self.x)});
  300. }
  301. fc.animator.start();
  302. }
  303. function FireworkParticle(oC,isRandom,type,baseX,baseY,obeyBoundaries) {
  304. var self = this;
  305. this.oC = oC;
  306. this.oID = 'fp'+(fc.gOID++); // may be unneeded
  307. this.o = fc.oFP.cloneNode(true);
  308. this.obeyBoundaries = obeyBoundaries;
  309. // set type: index becomes Y offset (for background image)
  310. this.type = null;
  311. this.oImg = this.o.getElementsByTagName('img')[0];
  312. this.oImg._src = this.oImg.src;
  313. this.o.style.display = 'none';
  314. this.baseX = baseX;
  315. this.baseY = baseY;
  316. this.x = 0;
  317. this.y = 0;
  318. this.vx = 0;
  319. this.vy = 0;
  320. this.frame = 0;
  321. this.tween = [];
  322. this.active = null;
  323. this.tweenType = 'blast';
  324. this.states = [];
  325. this.state = parseInt(Math.random()*3, 10);
  326. this.isRandom = isRandom;
  327. this._mt = 5;
  328. this.moveTo = function(x,y) {
  329. self.o.style.left = x+'px';
  330. self.o.style.top = y+'px';
  331. self.vx = x-self.x;
  332. self.vy = y-self.y;
  333. self.x = x;
  334. self.y = y;
  335. };
  336. this.moveRel = function() {
  337. // continue last moveTo() pattern, bouncing off walls if applicable
  338. var toX = self.x+self.vx;
  339. var toY = self.y+self.vy;
  340. if (self.obeyBoundaries) {
  341. var xMax = fc.canvasX-self.baseX-fc.particleXY;
  342. var yMax = fc.canvasY-self.baseY-fc.particleXY;
  343. var yMin = fc.scrollY;
  344. if (self.vx>=0) {
  345. if (toX>=xMax) {
  346. self.vx *= -1;
  347. }
  348. } else if (self.vx<0 && toX+self.baseX<=0) {
  349. self.vx *= -1;
  350. }
  351. if (self.vy>=0) {
  352. if (toY>=yMax) {
  353. self.vy *= -1;
  354. }
  355. } else if (self.vy<0) {
  356. if (toY+self.baseY-yMin<=0) {
  357. self.vy *= -1;
  358. }
  359. }
  360. }
  361. self.moveTo(self.x+self.vx,self.y+self.vy);
  362. };
  363. this.setOpacity = function(n) { // where n = 0..100
  364. self.oImg.style.marginLeft = -100+(n*fc.particleXY/10)+'px';
  365. };
  366. this.nextState = function() {
  367. var vis = self.o.style.visibility;
  368. if (self.state == 2 && vis != 'hidden') {
  369. self.o.style.visibility = 'hidden';
  370. } else if (self.state != 2 && vis == 'hidden') {
  371. self.o.style.visibility = 'visible';
  372. }
  373. self.state = parseInt(Math.random()*3, 10);
  374. };
  375. this.slideTo = function(x1,y1) {
  376. // writeDebug('slideTo (x/y): '+x1+','+y1);
  377. if (self.isRandom) {
  378. // randomize a bit
  379. x1 += (x1*0.2*(Math.random()>0.5?1:-1));
  380. y1 += (y1*0.2*(Math.random()>0.5?1:-1));
  381. }
  382. self.tween = [fc.animator.createTween(self.x,x1,self.tweenType),fc.animator.createTween(self.y,y1,self.tweenType)];
  383. // prevent X overflow (scrolling)
  384. var xMax = fc.canvasX-fc.particleXY;
  385. var yMax = fc.canvasY-fc.particleXY;
  386. var xMin = fc.particleXY-self.baseX;
  387. var yMin = fc.scrollY;
  388. var toX = null;
  389. var toY = null;
  390. if (self.obeyBoundaries) {
  391. for (var i=self.tween[0].length; i--;) {
  392. // bounce off walls where applicable
  393. toX = self.tween[0][i].data+self.baseX;
  394. toY = self.tween[1][i].data+self.baseY;
  395. if (toX>=xMax) {
  396. self.tween[0][i].data -= (toX-xMax)*2;
  397. // self.tween[0][i].event = 'bounce';
  398. } else if (toX<0) {
  399. self.tween[0][i].data -= (toX*2);
  400. // self.tween[0][i].event = 'bounce';
  401. }
  402. if (toY>=yMax) {
  403. self.tween[1][i].data -= (toY-yMax)*2;
  404. // self.tween[1][i].event = 'bounce';
  405. } else if (toY-yMin<=0) {
  406. self.tween[1][i].data -= (toY-yMin)*2;
  407. // self.tween[1][i].event = 'bounce';
  408. }
  409. }
  410. }
  411. };
  412. this.animate = function() {
  413. var f0 = self.tween[0][self.frame].data;
  414. var f1 = self.tween[1][self.frame].data;
  415. self.moveTo(f0,f1);
  416. // possible bounce event/sound hooks
  417. // if (self.tween[0][self.frame].event) soundManager.play(self.tween[0][self.frame].event);
  418. // if (self.tween[1][self.frame].event) soundManager.play(self.tween[1][self.frame].event);
  419. if (self.frame++>=self.tween[0].length-1) {
  420. if (self._oncomplete) {
  421. self._oncomplete();
  422. }
  423. self._oncomplete = null;
  424. self.active = false;
  425. self.frame = 0;
  426. return false;
  427. } else if (self.frame>10) {
  428. self.nextState();
  429. }
  430. return true;
  431. };
  432. this.destructor = function() {
  433. self.oImg = null;
  434. self.oC.removeChild(self.o);
  435. self.oC = null;
  436. self.o = null;
  437. };
  438. this.setType = function(t) {
  439. self.type = t;
  440. self.oImg.style.marginTop = -(fc.particleXY*t)+'px';
  441. };
  442. self.setType(type);
  443. self.oC.appendChild(self.o);
  444. }
  445. function createFirework(nRadius,nParticles,nCircles,nBurstType,startX,startY,burstX,burstY,allowRandom,obeyBoundaries) {
  446. // check all arguments, supply random defaults if needed
  447. var tmp = '';
  448. for (var i in arguments) {
  449. if (arguments.hasOwnProperty(i)) {
  450. tmp += i+',';
  451. }
  452. }
  453. writeDebug('createFirework('+tmp+')');
  454. if (fc.isEmpty(startX)) {
  455. startX = parseInt(Math.random()*fc.canvasX, 10);
  456. } else {
  457. startX = parseInt(fc.canvasX*startX/100, 10);
  458. }
  459. if (fc.isEmpty(startY)) {
  460. startY = fc.canvasY-fc.particleXY;
  461. } else {
  462. startY = fc.canvasY-fc.screenY+parseInt(fc.screenY*startY/100, 10);
  463. }
  464. if (fc.isEmpty(burstX)) {
  465. burstX = parseInt(fc.canvasX*0.1+(Math.random()*fc.canvasX*0.8), 10);
  466. } else {
  467. burstX = parseInt(fc.canvasX*burstX/100, 10);
  468. }
  469. if (fc.isEmpty(burstY)) {
  470. burstY = fc.canvasY-parseInt(Math.random()*fc.screenY, 10);
  471. } else {
  472. burstY = fc.canvasY-parseInt(fc.screenY*(100-burstY)/100, 10);
  473. }
  474. if (fc.isEmpty(nBurstType)) {
  475. nBurstType = parseInt(Math.random()*fc.particleTypes, 10);
  476. }
  477. if (fc.isEmpty(nRadius)) {
  478. nRadius = 64+parseInt(Math.random()*fc.screenY*0.75, 10);
  479. } else if (nRadius.toString().indexOf('%')>=0) {
  480. nRadius = parseInt(parseInt(nRadius, 10)/100*fc.screenY, 10);
  481. } else if (nRadius.toString().indexOf('.')>=0) {
  482. nRadius = parseInt(nRadius*fc.screenY, 10);
  483. } else {
  484. nRadius = parseInt(nRadius*fc.screenY/100, 10);
  485. }
  486. if (fc.isEmpty(nParticles)) {
  487. nParticles = 4+parseInt(Math.random()*64, 10);
  488. }
  489. if (fc.isEmpty(nCircles)) {
  490. nCircles = Math.random()>0.5?2:1;
  491. }
  492. if (fc.isEmpty(allowRandom)) {
  493. allowRandom = Math.random()>0.5;
  494. }
  495. if (fc.isEmpty(obeyBoundaries)) {
  496. obeyBoundaries = Math.random()>0.5;
  497. }
  498. // update screen coordinates
  499. fc.getWindowCoords();
  500. fc.fireworks[fc.fireworks.length] = new Firework(document.getElementById('fireContainer'),startX,startY,burstX,burstY,nBurstType,nRadius,nParticles,nCircles,allowRandom,obeyBoundaries);
  501. }
  502. soundManager.url = '../js/fireworks/swf/';
  503. soundManager.useHighPerformance = true;
  504. soundManager.useHTML5Audio = true;
  505. soundManager.wmode = 'transparent';
  506. soundManager.onready(function() {
  507. var sounds = {
  508. 'fire0': 'boom3.mp3',
  509. 'fire1': 'boom4.mp3',
  510. 'boom0': 'boom1.mp3',
  511. 'boom1': 'boom2.mp3',
  512. 'boom2': 'pop1.mp3',
  513. 'boom3': 'pop2.mp3',
  514. 'boom4': 'pop3.mp3',
  515. 'boom5': 'pop4.mp3',
  516. 'boom6': 'pop5.mp3',
  517. 'boom7': 'pop6.mp3'
  518. };
  519. for (var item in sounds) {
  520. if (sounds.hasOwnProperty(item)) {
  521. soundManager.createSound({
  522. id: item,
  523. url: '/js/fireworks/audio/' + sounds[item],
  524. autoLoad: true
  525. });
  526. }
  527. }
  528. });
  529. fc = new FireworksController();
  530. // create null objects if APIs not present
  531. if (typeof(writeDebug)=='undefined') {
  532. window.writeDebug = function() {
  533. return false;
  534. };
  535. }
  536. function addEventHandler(o,evtName,evtHandler) {
  537. return (typeof(attachEvent)=='undefined'?o.addEventListener(evtName,evtHandler,false):o.attachEvent('on'+evtName,evtHandler));
  538. }
  539. function removeEventHandler(o,evtName,evtHandler) {
  540. return (typeof(attachEvent)=='undefined'?o.removeEventListener(evtName,evtHandler,false):o.detachEvent('on'+evtName,evtHandler));
  541. }
  542. addEventHandler(window,'resize',fc.getWindowCoords);
  543. addEventHandler(window,'scroll',fc.getWindowCoords);
  544. addEventHandler(window,'load',fc.init);
  545. addEventHandler(window,'unload',fc.destructor);