sonic.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /*
  2. * Sonic 0.3
  3. * --
  4. * https://github.com/padolsey/Sonic
  5. */
  6. (function(){
  7. var emptyFn = function(){};
  8. function Sonic(d) {
  9. this.converter = d.converter;
  10. this.data = d.path || d.data;
  11. this.imageData = [];
  12. this.multiplier = d.multiplier || 1;
  13. this.padding = d.padding || 0;
  14. this.fps = d.fps || 25;
  15. this.stepsPerFrame = ~~d.stepsPerFrame || 1;
  16. this.trailLength = d.trailLength || 1;
  17. this.pointDistance = d.pointDistance || .05;
  18. this.domClass = d.domClass || 'sonic';
  19. this.backgroundColor = d.backgroundColor || 'rgba(0,0,0,0)';
  20. this.fillColor = d.fillColor;
  21. this.strokeColor = d.strokeColor;
  22. this.stepMethod = typeof d.step == 'string' ?
  23. stepMethods[d.step] :
  24. d.step || stepMethods.square;
  25. this._setup = d.setup || emptyFn;
  26. this._teardown = d.teardown || emptyFn;
  27. this._preStep = d.preStep || emptyFn;
  28. this.pixelRatio = d.pixelRatio || null;
  29. this.width = d.width;
  30. this.height = d.height;
  31. this.fullWidth = this.width + 2 * this.padding;
  32. this.fullHeight = this.height + 2 * this.padding;
  33. this.domClass = d.domClass || 'sonic';
  34. this.setup();
  35. }
  36. var argTypes = Sonic.argTypes = {
  37. DIM: 1,
  38. DEGREE: 2,
  39. RADIUS: 3,
  40. OTHER: 0
  41. };
  42. var argSignatures = Sonic.argSignatures = {
  43. arc: [1, 1, 3, 2, 2, 0],
  44. bezier: [1, 1, 1, 1, 1, 1, 1, 1],
  45. line: [1,1,1,1]
  46. };
  47. var pathMethods = Sonic.pathMethods = {
  48. bezier: function(t, p0x, p0y, p1x, p1y, c0x, c0y, c1x, c1y) {
  49. t = 1-t;
  50. var i = 1-t,
  51. x = t*t,
  52. y = i*i,
  53. a = x*t,
  54. b = 3 * x * i,
  55. c = 3 * t * y,
  56. d = y * i;
  57. return [
  58. a * p0x + b * c0x + c * c1x + d * p1x,
  59. a * p0y + b * c0y + c * c1y + d * p1y
  60. ]
  61. },
  62. arc: function(t, cx, cy, radius, start, end) {
  63. var point = (end - start) * t + start;
  64. var ret = [
  65. (Math.cos(point) * radius) + cx,
  66. (Math.sin(point) * radius) + cy
  67. ];
  68. ret.angle = point;
  69. ret.t = t;
  70. return ret;
  71. },
  72. line: function(t, sx, sy, ex, ey) {
  73. return [
  74. (ex - sx) * t + sx,
  75. (ey - sy) * t + sy
  76. ]
  77. }
  78. };
  79. var stepMethods = Sonic.stepMethods = {
  80. square: function(point, i, f, color, alpha) {
  81. this._.fillRect(point.x - 3, point.y - 3, 6, 6);
  82. },
  83. fader: function(point, i, f, color, alpha) {
  84. this._.beginPath();
  85. if (this._last) {
  86. this._.moveTo(this._last.x, this._last.y);
  87. }
  88. this._.lineTo(point.x, point.y);
  89. this._.closePath();
  90. this._.stroke();
  91. this._last = point;
  92. }
  93. }
  94. Sonic.prototype = {
  95. calculatePixelRatio: function(){
  96. var devicePixelRatio = window.devicePixelRatio || 1;
  97. var backingStoreRatio = this._.webkitBackingStorePixelRatio
  98. || this._.mozBackingStorePixelRatio
  99. || this._.msBackingStorePixelRatio
  100. || this._.oBackingStorePixelRatio
  101. || this._.backingStorePixelRatio
  102. || 1;
  103. return devicePixelRatio / backingStoreRatio;
  104. },
  105. setup: function() {
  106. var args,
  107. type,
  108. method,
  109. value,
  110. data = this.data;
  111. this.canvas = document.createElement('canvas');
  112. this._ = this.canvas.getContext('2d');
  113. if(this.pixelRatio == null){
  114. this.pixelRatio = this.calculatePixelRatio();
  115. }
  116. this.canvas.className = this.domClass;
  117. if(this.pixelRatio != 1){
  118. this.canvas.style.height = this.fullHeight + 'px';
  119. this.canvas.style.width = this.fullWidth + 'px';
  120. this.fullHeight *= this.pixelRatio;
  121. this.fullWidth *= this.pixelRatio;
  122. this.canvas.height = this.fullHeight;
  123. this.canvas.width = this.fullWidth;
  124. this._.scale(this.pixelRatio, this.pixelRatio);
  125. } else{
  126. this.canvas.height = this.fullHeight;
  127. this.canvas.width = this.fullWidth;
  128. }
  129. this.points = [];
  130. for (var i = -1, l = data.length; ++i < l;) {
  131. args = data[i].slice(1);
  132. method = data[i][0];
  133. if (method in argSignatures) for (var a = -1, al = args.length; ++a < al;) {
  134. type = argSignatures[method][a];
  135. value = args[a];
  136. switch (type) {
  137. case argTypes.RADIUS:
  138. value *= this.multiplier;
  139. break;
  140. case argTypes.DIM:
  141. value *= this.multiplier;
  142. value += this.padding;
  143. break;
  144. case argTypes.DEGREE:
  145. value *= Math.PI/180;
  146. break;
  147. };
  148. args[a] = value;
  149. }
  150. args.unshift(0);
  151. for (var r, pd = this.pointDistance, t = pd; t <= 1; t += pd) {
  152. // Avoid crap like 0.15000000000000002
  153. t = Math.round(t*1/pd) / (1/pd);
  154. args[0] = t;
  155. r = pathMethods[method].apply(null, args);
  156. this.points.push({
  157. x: r[0],
  158. y: r[1],
  159. progress: t
  160. });
  161. }
  162. }
  163. this.frame = 0;
  164. if (this.converter && this.converter.setup) {
  165. this.converter.setup(this);
  166. }
  167. },
  168. prep: function(frame) {
  169. if (frame in this.imageData) {
  170. return;
  171. }
  172. this._.clearRect(0, 0, this.fullWidth, this.fullHeight);
  173. this._.fillStyle = this.backgroundColor;
  174. this._.fillRect(0, 0, this.fullWidth, this.fullHeight);
  175. var points = this.points,
  176. pointsLength = points.length,
  177. pd = this.pointDistance,
  178. point,
  179. index,
  180. frameD;
  181. this._setup();
  182. for (var i = -1, l = pointsLength*this.trailLength; ++i < l && !this.stopped;) {
  183. index = frame + i;
  184. point = points[index] || points[index - pointsLength];
  185. if (!point) continue;
  186. this.alpha = Math.round(1000*(i/(l-1)))/1000;
  187. this._.globalAlpha = this.alpha;
  188. if (this.fillColor) {
  189. this._.fillStyle = this.fillColor;
  190. }
  191. if (this.strokeColor) {
  192. this._.strokeStyle = this.strokeColor;
  193. }
  194. frameD = frame/(this.points.length-1);
  195. indexD = i/(l-1);
  196. this._preStep(point, indexD, frameD);
  197. this.stepMethod(point, indexD, frameD);
  198. }
  199. this._teardown();
  200. this.imageData[frame] = (
  201. this._.getImageData(0, 0, this.fullWidth, this.fullWidth)
  202. );
  203. return true;
  204. },
  205. draw: function() {
  206. if (!this.prep(this.frame)) {
  207. this._.clearRect(0, 0, this.fullWidth, this.fullWidth);
  208. this._.putImageData(
  209. this.imageData[this.frame],
  210. 0, 0
  211. );
  212. }
  213. if (this.converter && this.converter.step) {
  214. this.converter.step(this);
  215. }
  216. if (!this.iterateFrame()) {
  217. if (this.converter && this.converter.teardown) {
  218. this.converter.teardown(this);
  219. this.converter = null;
  220. }
  221. }
  222. },
  223. iterateFrame: function() {
  224. this.frame += this.stepsPerFrame;
  225. if (this.frame >= this.points.length) {
  226. this.frame = 0;
  227. return false;
  228. }
  229. return true;
  230. },
  231. play: function() {
  232. this.stopped = false;
  233. var hoc = this;
  234. this.timer = setInterval(function(){
  235. hoc.draw();
  236. }, 1000 / this.fps);
  237. },
  238. stop: function() {
  239. this.stopped = true;
  240. this.timer && clearInterval(this.timer);
  241. }
  242. };
  243. window.Sonic = Sonic;
  244. }());