activity-indicator.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /*!
  2. * NETEYE Activity Indicator jQuery Plugin
  3. *
  4. * Copyright (c) 2010 NETEYE GmbH
  5. * Licensed under the MIT license
  6. *
  7. * Author: Felix Gnass [fgnass at neteye dot de]
  8. * Version: @{VERSION}
  9. */
  10. /**
  11. * Plugin that renders a customisable activity indicator (spinner) using SVG or VML.
  12. */
  13. (function($) {
  14. $.fn.activity = function(opts) {
  15. this.each(function() {
  16. var $this = $(this);
  17. var el = $this.data('activity');
  18. if (el) {
  19. clearInterval(el.data('interval'));
  20. el.remove();
  21. $this.removeData('activity');
  22. }
  23. if (opts !== false) {
  24. opts = $.extend({color: $this.css('color')}, $.fn.activity.defaults, opts);
  25. el = render($this, opts).css('position', 'absolute').prependTo(opts.outside ? 'body' : $this);
  26. var h = $this.outerHeight() - el.height();
  27. var w = $this.outerWidth() - el.width();
  28. var margin = {
  29. top: opts.valign == 'top' ? opts.padding : opts.valign == 'bottom' ? h - opts.padding : Math.floor(h / 2),
  30. left: opts.align == 'left' ? opts.padding : opts.align == 'right' ? w - opts.padding : Math.floor(w / 2)
  31. };
  32. var offset = $this.offset();
  33. if (opts.outside) {
  34. el.css({top: offset.top + 'px', left: offset.left + 'px'});
  35. }
  36. else {
  37. margin.top -= el.offset().top - offset.top;
  38. margin.left -= el.offset().left - offset.left;
  39. }
  40. el.css({marginTop: margin.top + 'px', marginLeft: margin.left + 'px'});
  41. animate(el, opts.segments, Math.round(10 / opts.speed) / 10);
  42. $this.data('activity', el);
  43. }
  44. });
  45. return this;
  46. };
  47. $.fn.activity.defaults = {
  48. segments: 12,
  49. space: 3,
  50. length: 7,
  51. width: 4,
  52. speed: 1.2,
  53. align: 'center',
  54. valign: 'center',
  55. padding: 4
  56. };
  57. $.fn.activity.getOpacity = function(opts, i) {
  58. var steps = opts.steps || opts.segments-1;
  59. var end = opts.opacity !== undefined ? opts.opacity : 1/steps;
  60. return 1 - Math.min(i, steps) * (1 - end) / steps;
  61. };
  62. /**
  63. * Default rendering strategy. If neither SVG nor VML is available, a div with class-name 'busy'
  64. * is inserted, that can be styled with CSS to display an animated gif as fallback.
  65. */
  66. var render = function() {
  67. return $('<div>').addClass('busy');
  68. };
  69. /**
  70. * The default animation strategy does nothing as we expect an animated gif as fallback.
  71. */
  72. var animate = function() {
  73. };
  74. /**
  75. * Utility function to create elements in the SVG namespace.
  76. */
  77. function svg(tag, attr) {
  78. var el = document.createElementNS("http://www.w3.org/2000/svg", tag || 'svg');
  79. if (attr) {
  80. $.each(attr, function(k, v) {
  81. el.setAttributeNS(null, k, v);
  82. });
  83. }
  84. return $(el);
  85. }
  86. if (document.createElementNS && document.createElementNS( "http://www.w3.org/2000/svg", "svg").createSVGRect) {
  87. // =======================================================================================
  88. // SVG Rendering
  89. // =======================================================================================
  90. /**
  91. * Rendering strategy that creates a SVG tree.
  92. */
  93. render = function(target, d) {
  94. var innerRadius = d.width*2 + d.space;
  95. var r = (innerRadius + d.length + Math.ceil(d.width / 2) + 1);
  96. var el = svg().width(r*2).height(r*2);
  97. var g = svg('g', {
  98. 'stroke-width': d.width,
  99. 'stroke-linecap': 'round',
  100. stroke: d.color
  101. }).appendTo(svg('g', {transform: 'translate('+ r +','+ r +')'}).appendTo(el));
  102. for (var i = 0; i < d.segments; i++) {
  103. g.append(svg('line', {
  104. x1: 0,
  105. y1: innerRadius,
  106. x2: 0,
  107. y2: innerRadius + d.length,
  108. transform: 'rotate(' + (360 / d.segments * i) + ', 0, 0)',
  109. opacity: $.fn.activity.getOpacity(d, i)
  110. }));
  111. }
  112. return $('<div>').append(el).width(2*r).height(2*r);
  113. };
  114. // Check if Webkit CSS animations are available, as they work much better on the iPad
  115. // than setTimeout() based animations.
  116. if (document.createElement('div').style.WebkitAnimationName !== undefined) {
  117. var animations = {};
  118. /**
  119. * Animation strategy that uses dynamically created CSS animation rules.
  120. */
  121. animate = function(el, steps, duration) {
  122. if (!animations[steps]) {
  123. var name = 'spin' + steps;
  124. var rule = '@-webkit-keyframes '+ name +' {';
  125. for (var i=0; i < steps; i++) {
  126. var p1 = Math.round(100000 / steps * i) / 1000;
  127. var p2 = Math.round(100000 / steps * (i+1) - 1) / 1000;
  128. var value = '% { -webkit-transform:rotate(' + Math.round(360 / steps * i) + 'deg); }\n';
  129. rule += p1 + value + p2 + value;
  130. }
  131. rule += '100% { -webkit-transform:rotate(100deg); }\n}';
  132. document.styleSheets[0].insertRule(rule);
  133. animations[steps] = name;
  134. }
  135. el.css('-webkit-animation', animations[steps] + ' ' + duration +'s linear infinite');
  136. };
  137. }
  138. else {
  139. /**
  140. * Animation strategy that transforms a SVG element using setInterval().
  141. */
  142. animate = function(el, steps, duration) {
  143. var rotation = 0;
  144. var g = el.find('g g').get(0);
  145. el.data('interval', setInterval(function() {
  146. g.setAttributeNS(null, 'transform', 'rotate(' + (++rotation % steps * (360 / steps)) + ')');
  147. }, duration * 1000 / steps));
  148. };
  149. }
  150. }
  151. else {
  152. // =======================================================================================
  153. // VML Rendering
  154. // =======================================================================================
  155. var s = $('<shape>').css('behavior', 'url(#default#VML)');
  156. $('body').append(s);
  157. if (s.get(0).adj) {
  158. // VML support detected. Insert CSS rules for group, shape and stroke.
  159. var sheet = document.createStyleSheet();
  160. $.each(['group', 'shape', 'stroke'], function() {
  161. sheet.addRule(this, "behavior:url(#default#VML);");
  162. });
  163. /**
  164. * Rendering strategy that creates a VML tree.
  165. */
  166. render = function(target, d) {
  167. var innerRadius = d.width*2 + d.space;
  168. var r = (innerRadius + d.length + Math.ceil(d.width / 2) + 1);
  169. var s = r*2;
  170. var o = -Math.ceil(s/2);
  171. var el = $('<group>', {coordsize: s + ' ' + s, coordorigin: o + ' ' + o}).css({top: o, left: o, width: s, height: s});
  172. for (var i = 0; i < d.segments; i++) {
  173. el.append($('<shape>', {path: 'm ' + innerRadius + ',0 l ' + (innerRadius + d.length) + ',0'}).css({
  174. width: s,
  175. height: s,
  176. rotation: (360 / d.segments * i) + 'deg'
  177. }).append($('<stroke>', {color: d.color, weight: d.width + 'px', endcap: 'round', opacity: $.fn.activity.getOpacity(d, i)})));
  178. }
  179. return $('<group>', {coordsize: s + ' ' + s}).css({width: s, height: s, overflow: 'hidden'}).append(el);
  180. };
  181. /**
  182. * Animation strategy that modifies the VML rotation property using setInterval().
  183. */
  184. animate = function(el, steps, duration) {
  185. var rotation = 0;
  186. var g = el.get(0);
  187. el.data('interval', setInterval(function() {
  188. g.style.rotation = ++rotation % steps * (360 / steps);
  189. }, duration * 1000 / steps));
  190. };
  191. }
  192. $(s).remove();
  193. }
  194. })(jQuery);