spin.js 11 KB

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