spin.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. //fgnass.github.com/spin.js#v1.3
  2. /**
  3. * Copyright (c) 2011-2013 Felix Gnass
  4. * Licensed under the MIT license
  5. */
  6. (function(root, factory) {
  7. /* CommonJS */
  8. if (typeof exports == 'object') module.exports = factory()
  9. /* AMD module */
  10. else if (typeof define == 'function' && define.amd) define(factory)
  11. /* Browser global */
  12. else root.Spinner = factory()
  13. }
  14. (this, function() {
  15. "use strict";
  16. var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
  17. , animations = {} /* Animation rules keyed by their name */
  18. , useCssAnimations /* Whether to use CSS animations or setTimeout */
  19. /**
  20. * Utility function to create elements. If no tag name is given,
  21. * a DIV is created. Optionally properties can be passed.
  22. */
  23. function createEl(tag, prop) {
  24. var el = document.createElement(tag || 'div')
  25. , n
  26. for(n in prop) el[n] = prop[n]
  27. return el
  28. }
  29. /**
  30. * Appends children and returns the parent.
  31. */
  32. function ins(parent /* child1, child2, ...*/) {
  33. for (var i=1, n=arguments.length; i<n; i++)
  34. parent.appendChild(arguments[i])
  35. return parent
  36. }
  37. /**
  38. * Insert a new stylesheet to hold the @keyframe or VML rules.
  39. */
  40. var sheet = (function() {
  41. var el = createEl('style', {type : 'text/css'})
  42. ins(document.getElementsByTagName('head')[0], el)
  43. return el.sheet || el.styleSheet
  44. }())
  45. /**
  46. * Creates an opacity keyframe animation rule and returns its name.
  47. * Since most mobile Webkits have timing issues with animation-delay,
  48. * we create separate rules for each line/segment.
  49. */
  50. function addAnimation(alpha, trail, i, lines) {
  51. var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
  52. , start = 0.01 + i/lines * 100
  53. , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
  54. , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
  55. , pre = prefix && '-' + prefix + '-' || ''
  56. if (!animations[name]) {
  57. sheet.insertRule(
  58. '@' + pre + 'keyframes ' + name + '{' +
  59. '0%{opacity:' + z + '}' +
  60. start + '%{opacity:' + alpha + '}' +
  61. (start+0.01) + '%{opacity:1}' +
  62. (start+trail) % 100 + '%{opacity:' + alpha + '}' +
  63. '100%{opacity:' + z + '}' +
  64. '}', sheet.cssRules.length)
  65. animations[name] = 1
  66. }
  67. return name
  68. }
  69. /**
  70. * Tries various vendor prefixes and returns the first supported property.
  71. */
  72. function vendor(el, prop) {
  73. var s = el.style
  74. , pp
  75. , i
  76. if(s[prop] !== undefined) return prop
  77. prop = prop.charAt(0).toUpperCase() + prop.slice(1)
  78. for(i=0; i<prefixes.length; i++) {
  79. pp = prefixes[i]+prop
  80. if(s[pp] !== undefined) return pp
  81. }
  82. }
  83. /**
  84. * Sets multiple style properties at once.
  85. */
  86. function css(el, prop) {
  87. for (var n in prop)
  88. el.style[vendor(el, n)||n] = prop[n]
  89. return el
  90. }
  91. /**
  92. * Fills in default values.
  93. */
  94. function merge(obj) {
  95. for (var i=1; i < arguments.length; i++) {
  96. var def = arguments[i]
  97. for (var n in def)
  98. if (obj[n] === undefined) obj[n] = def[n]
  99. }
  100. return obj
  101. }
  102. /**
  103. * Returns the absolute page-offset of the given element.
  104. */
  105. function pos(el) {
  106. var o = { x:el.offsetLeft, y:el.offsetTop }
  107. while((el = el.offsetParent))
  108. o.x+=el.offsetLeft, o.y+=el.offsetTop
  109. return o
  110. }
  111. // Built-in defaults
  112. var defaults = {
  113. lines: 12, // The number of lines to draw
  114. length: 7, // The length of each line
  115. width: 5, // The line thickness
  116. radius: 10, // The radius of the inner circle
  117. rotate: 0, // Rotation offset
  118. corners: 1, // Roundness (0..1)
  119. color: '#000', // #rgb or #rrggbb
  120. direction: 1, // 1: clockwise, -1: counterclockwise
  121. speed: 1, // Rounds per second
  122. trail: 100, // Afterglow percentage
  123. opacity: 1/4, // Opacity of the lines
  124. fps: 20, // Frames per second when using setTimeout()
  125. zIndex: 2e9, // Use a high z-index by default
  126. className: 'spinner', // CSS class to assign to the element
  127. top: 'auto', // center vertically
  128. left: 'auto', // center horizontally
  129. position: 'relative' // element position
  130. }
  131. /** The constructor */
  132. function Spinner(o) {
  133. if (typeof this == 'undefined') return new Spinner(o)
  134. this.opts = merge(o || {}, Spinner.defaults, defaults)
  135. }
  136. // Global defaults that override the built-ins:
  137. Spinner.defaults = {}
  138. merge(Spinner.prototype, {
  139. /**
  140. * Adds the spinner to the given target element. If this instance is already
  141. * spinning, it is automatically removed from its previous target b calling
  142. * stop() internally.
  143. */
  144. spin: function(target) {
  145. this.stop()
  146. var self = this
  147. , o = self.opts
  148. , el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
  149. , mid = o.radius+o.length+o.width
  150. , ep // element position
  151. , tp // target position
  152. if (target) {
  153. target.insertBefore(el, target.firstChild||null)
  154. tp = pos(target)
  155. ep = pos(el)
  156. css(el, {
  157. left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
  158. top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px'
  159. })
  160. }
  161. el.setAttribute('role', 'progressbar')
  162. self.lines(el, self.opts)
  163. if (!useCssAnimations) {
  164. // No CSS animation support, use setTimeout() instead
  165. var i = 0
  166. , start = (o.lines - 1) * (1 - o.direction) / 2
  167. , alpha
  168. , fps = o.fps
  169. , f = fps/o.speed
  170. , ostep = (1-o.opacity) / (f*o.trail / 100)
  171. , astep = f/o.lines
  172. ;(function anim() {
  173. i++;
  174. for (var j = 0; j < o.lines; j++) {
  175. alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
  176. self.opacity(el, j * o.direction + start, alpha, o)
  177. }
  178. self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
  179. })()
  180. }
  181. return self
  182. },
  183. /**
  184. * Stops and removes the Spinner.
  185. */
  186. stop: function() {
  187. var el = this.el
  188. if (el) {
  189. clearTimeout(this.timeout)
  190. if (el.parentNode) el.parentNode.removeChild(el)
  191. this.el = undefined
  192. }
  193. return this
  194. },
  195. /**
  196. * Internal method that draws the individual lines. Will be overwritten
  197. * in VML fallback mode below.
  198. */
  199. lines: function(el, o) {
  200. var i = 0
  201. , start = (o.lines - 1) * (1 - o.direction) / 2
  202. , seg
  203. function fill(color, shadow) {
  204. return css(createEl(), {
  205. position: 'absolute',
  206. width: (o.length+o.width) + 'px',
  207. height: o.width + 'px',
  208. background: color,
  209. boxShadow: shadow,
  210. transformOrigin: 'left',
  211. transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
  212. borderRadius: (o.corners * o.width>>1) + 'px'
  213. })
  214. }
  215. for (; i < o.lines; i++) {
  216. seg = css(createEl(), {
  217. position: 'absolute',
  218. top: 1+~(o.width/2) + 'px',
  219. transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
  220. opacity: o.opacity,
  221. animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
  222. })
  223. if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
  224. ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')))
  225. }
  226. return el
  227. },
  228. /**
  229. * Internal method that adjusts the opacity of a single line.
  230. * Will be overwritten in VML fallback mode below.
  231. */
  232. opacity: function(el, i, val) {
  233. if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
  234. }
  235. })
  236. function initVML() {
  237. /* Utility function to create a VML tag */
  238. function vml(tag, attr) {
  239. return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
  240. }
  241. // No CSS transforms but VML support, add a CSS rule for VML elements:
  242. sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
  243. Spinner.prototype.lines = function(el, o) {
  244. var r = o.length+o.width
  245. , s = 2*r
  246. function grp() {
  247. return css(
  248. vml('group', {
  249. coordsize: s + ' ' + s,
  250. coordorigin: -r + ' ' + -r
  251. }),
  252. { width: s, height: s }
  253. )
  254. }
  255. var margin = -(o.width+o.length)*2 + 'px'
  256. , g = css(grp(), {position: 'absolute', top: margin, left: margin})
  257. , i
  258. function seg(i, dx, filter) {
  259. ins(g,
  260. ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
  261. ins(css(vml('roundrect', {arcsize: o.corners}), {
  262. width: r,
  263. height: o.width,
  264. left: o.radius,
  265. top: -o.width>>1,
  266. filter: filter
  267. }),
  268. vml('fill', {color: o.color, opacity: o.opacity}),
  269. vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
  270. )
  271. )
  272. )
  273. }
  274. if (o.shadow)
  275. for (i = 1; i <= o.lines; i++)
  276. seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
  277. for (i = 1; i <= o.lines; i++) seg(i)
  278. return ins(el, g)
  279. }
  280. Spinner.prototype.opacity = function(el, i, val, o) {
  281. var c = el.firstChild
  282. o = o.shadow && o.lines || 0
  283. if (c && i+o < c.childNodes.length) {
  284. c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
  285. if (c) c.opacity = val
  286. }
  287. }
  288. }
  289. var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
  290. if (!vendor(probe, 'transform') && probe.adj) initVML()
  291. else useCssAnimations = vendor(probe, 'animation')
  292. return Spinner
  293. }));