util.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /**
  2. * Extend object a with the properties of object b.
  3. * If there's a conflict, object b takes precedence.
  4. *
  5. * @param {object} a
  6. * @param {object} b
  7. */
  8. export const extend = ( a, b ) => {
  9. for( let i in b ) {
  10. a[ i ] = b[ i ];
  11. }
  12. return a;
  13. }
  14. /**
  15. * Converts the target object to an array.
  16. *
  17. * @param {object} o
  18. * @return {object[]}
  19. */
  20. export const toArray = ( o ) => {
  21. return Array.prototype.slice.call( o );
  22. }
  23. /**
  24. * Utility for deserializing a value.
  25. *
  26. * @param {*} value
  27. * @return {*}
  28. */
  29. export const deserialize = ( value ) => {
  30. if( typeof value === 'string' ) {
  31. if( value === 'null' ) return null;
  32. else if( value === 'true' ) return true;
  33. else if( value === 'false' ) return false;
  34. else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
  35. }
  36. return value;
  37. }
  38. /**
  39. * Measures the distance in pixels between point a
  40. * and point b.
  41. *
  42. * @param {object} a point with x/y properties
  43. * @param {object} b point with x/y properties
  44. *
  45. * @return {number}
  46. */
  47. export const distanceBetween = ( a, b ) => {
  48. let dx = a.x - b.x,
  49. dy = a.y - b.y;
  50. return Math.sqrt( dx*dx + dy*dy );
  51. }
  52. /**
  53. * Applies a CSS transform to the target element.
  54. *
  55. * @param {HTMLElement} element
  56. * @param {string} transform
  57. */
  58. export const transformElement = ( element, transform ) => {
  59. element.style.transform = transform;
  60. }
  61. /**
  62. * Find the closest parent that matches the given
  63. * selector.
  64. *
  65. * @param {HTMLElement} target The child element
  66. * @param {String} selector The CSS selector to match
  67. * the parents against
  68. *
  69. * @return {HTMLElement} The matched parent or null
  70. * if no matching parent was found
  71. */
  72. export const closestParent = ( target, selector ) => {
  73. let parent = target.parentNode;
  74. while( parent ) {
  75. // There's some overhead doing this each time, we don't
  76. // want to rewrite the element prototype but should still
  77. // be enough to feature detect once at startup...
  78. let matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
  79. // If we find a match, we're all set
  80. if( matchesMethod && matchesMethod.call( parent, selector ) ) {
  81. return parent;
  82. }
  83. // Keep searching
  84. parent = parent.parentNode;
  85. }
  86. return null;
  87. }
  88. /**
  89. * Converts various color input formats to an {r:0,g:0,b:0} object.
  90. *
  91. * @param {string} color The string representation of a color
  92. * @example
  93. * colorToRgb('#000');
  94. * @example
  95. * colorToRgb('#000000');
  96. * @example
  97. * colorToRgb('rgb(0,0,0)');
  98. * @example
  99. * colorToRgb('rgba(0,0,0)');
  100. *
  101. * @return {{r: number, g: number, b: number, [a]: number}|null}
  102. */
  103. export const colorToRgb = ( color ) => {
  104. let hex3 = color.match( /^#([0-9a-f]{3})$/i );
  105. if( hex3 && hex3[1] ) {
  106. hex3 = hex3[1];
  107. return {
  108. r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
  109. g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
  110. b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
  111. };
  112. }
  113. let hex6 = color.match( /^#([0-9a-f]{6})$/i );
  114. if( hex6 && hex6[1] ) {
  115. hex6 = hex6[1];
  116. return {
  117. r: parseInt( hex6.substr( 0, 2 ), 16 ),
  118. g: parseInt( hex6.substr( 2, 2 ), 16 ),
  119. b: parseInt( hex6.substr( 4, 2 ), 16 )
  120. };
  121. }
  122. let rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
  123. if( rgb ) {
  124. return {
  125. r: parseInt( rgb[1], 10 ),
  126. g: parseInt( rgb[2], 10 ),
  127. b: parseInt( rgb[3], 10 )
  128. };
  129. }
  130. let rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
  131. if( rgba ) {
  132. return {
  133. r: parseInt( rgba[1], 10 ),
  134. g: parseInt( rgba[2], 10 ),
  135. b: parseInt( rgba[3], 10 ),
  136. a: parseFloat( rgba[4] )
  137. };
  138. }
  139. return null;
  140. }
  141. /**
  142. * Calculates brightness on a scale of 0-255.
  143. *
  144. * @param {string} color See colorToRgb for supported formats.
  145. * @see {@link colorToRgb}
  146. */
  147. export const colorBrightness = ( color ) => {
  148. if( typeof color === 'string' ) color = colorToRgb( color );
  149. if( color ) {
  150. return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
  151. }
  152. return null;
  153. }
  154. /**
  155. * Handling the fullscreen functionality via the fullscreen API
  156. *
  157. * @see http://fullscreen.spec.whatwg.org/
  158. * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
  159. */
  160. export const enterFullscreen = () => {
  161. let element = document.documentElement;
  162. // Check which implementation is available
  163. let requestMethod = element.requestFullscreen ||
  164. element.webkitRequestFullscreen ||
  165. element.webkitRequestFullScreen ||
  166. element.mozRequestFullScreen ||
  167. element.msRequestFullscreen;
  168. if( requestMethod ) {
  169. requestMethod.apply( element );
  170. }
  171. }
  172. /**
  173. * Injects the given CSS styles into the DOM.
  174. *
  175. * @param {string} value
  176. */
  177. export const injectStyleSheet = ( value ) => {
  178. let tag = document.createElement( 'style' );
  179. tag.type = 'text/css';
  180. if( tag.styleSheet ) {
  181. tag.styleSheet.cssText = value;
  182. }
  183. else {
  184. tag.appendChild( document.createTextNode( value ) );
  185. }
  186. document.getElementsByTagName( 'head' )[0].appendChild( tag );
  187. }
  188. /**
  189. * Loads a JavaScript file from the given URL and executes it.
  190. *
  191. * @param {string} url Address of the .js file to load
  192. * @param {function} callback Method to invoke when the script
  193. * has loaded and executed
  194. */
  195. export const loadScript = ( url, callback ) => {
  196. const script = document.createElement( 'script' );
  197. script.type = 'text/javascript';
  198. script.async = false;
  199. script.defer = false;
  200. script.src = url;
  201. if( typeof callback === 'function' ) {
  202. // Success callback
  203. script.onload = script.onreadystatechange = event => {
  204. if( event.type === 'load' || /loaded|complete/.test( script.readyState ) ) {
  205. // Kill event listeners
  206. script.onload = script.onreadystatechange = script.onerror = null;
  207. callback();
  208. }
  209. };
  210. // Error callback
  211. script.onerror = err => {
  212. // Kill event listeners
  213. script.onload = script.onreadystatechange = script.onerror = null;
  214. callback( new Error( 'Failed loading script: ' + script.src + '\n' + err ) );
  215. };
  216. }
  217. // Append the script at the end of <head>
  218. const head = document.querySelector( 'head' );
  219. head.insertBefore( script, head.lastChild );
  220. }