Home Reference Source Repository

lib/mixins/dom-styles-reader.js

  1. 'use babel'
  2.  
  3. import Mixin from 'mixto'
  4.  
  5. /**
  6. * This mixin is used by the `CanvasDrawer` in `MinimapElement` to
  7. * read the styles informations from the DOM to use when rendering
  8. * the `Minimap`.
  9. */
  10. export default class DOMStylesReader extends Mixin {
  11. /**
  12. * Returns the computed values for the given property and scope in the DOM.
  13. *
  14. * This function insert a dummy element in the DOM to compute
  15. * its style, return the specified property, and clear the content of the
  16. * dummy element.
  17. *
  18. * @param {Array<string>} scopes a list of classes reprensenting the scope
  19. * to build
  20. * @param {string} property the name of the style property to compute
  21. * @param {boolean} [shadowRoot=true] whether to compute the style inside
  22. * a shadow DOM or not
  23. * @param {boolean} [cache=true] whether to cache the computed value or not
  24. * @return {string} the computed property's value
  25. */
  26. retrieveStyleFromDom (scopes, property, shadowRoot = true, cache = true) {
  27. this.ensureCache()
  28.  
  29. let key = scopes.join(' ')
  30. let cachedData = this.constructor.domStylesCache[key]
  31.  
  32. if (cache && (cachedData ? cachedData[property] : void 0) != null) {
  33. return cachedData[property]
  34. }
  35.  
  36. this.ensureDummyNodeExistence(shadowRoot)
  37.  
  38. if (!cachedData) {
  39. this.constructor.domStylesCache[key] = cachedData = {}
  40. }
  41.  
  42. let parent = this.dummyNode
  43. for (let i = 0, len = scopes.length; i < len; i++) {
  44. let scope = scopes[i]
  45. let node = document.createElement('span')
  46. node.className = scope.replace(/\.+/g, ' ')
  47.  
  48. if (parent != null) { parent.appendChild(node) }
  49.  
  50. parent = node
  51. }
  52.  
  53. let style = window.getComputedStyle(parent)
  54. let filter = style.getPropertyValue('-webkit-filter')
  55. let value = style.getPropertyValue(property)
  56.  
  57. if (filter.indexOf('hue-rotate') > -1) {
  58. value = this.rotateHue(value, filter)
  59. }
  60.  
  61. if (value !== '') { cachedData[property] = value }
  62.  
  63. this.dummyNode.innerHTML = ''
  64. return value
  65. }
  66.  
  67. /**
  68. * Creates a DOM node container for all the operations that need to read
  69. * styles properties from DOM.
  70. *
  71. * @param {boolean} shadowRoot whether to create the dummy node in the shadow
  72. * DOM or not
  73. * @access private
  74. */
  75. ensureDummyNodeExistence (shadowRoot) {
  76. if (this.dummyNode == null) {
  77. /**
  78. * @access private
  79. */
  80. this.dummyNode = document.createElement('span')
  81. this.dummyNode.style.visibility = 'hidden'
  82. }
  83.  
  84. this.getDummyDOMRoot(shadowRoot).appendChild(this.dummyNode)
  85. }
  86.  
  87. /**
  88. * Ensures the presence of the cache object in the class that received
  89. * this mixin.
  90. *
  91. * @access private
  92. */
  93. ensureCache () {
  94. if (!this.constructor.domStylesCache) {
  95. this.constructor.domStylesCache = {}
  96. }
  97. }
  98.  
  99. /**
  100. * Invalidates the cache by emptying the cache object.
  101. */
  102. invalidateDOMStylesCache () {
  103. this.constructor.domStylesCache = {}
  104. }
  105.  
  106. /**
  107. * Invalidates the cache only for the first tokenization event.
  108. *
  109. * @access private
  110. */
  111. invalidateIfFirstTokenization () {
  112. if (this.constructor.hasTokenizedOnce) { return }
  113. this.invalidateDOMStylesCache()
  114. this.constructor.hasTokenizedOnce = true
  115. }
  116.  
  117. /**
  118. * Computes the output color of `value` with a rotated hue defined
  119. * in `filter`.
  120. *
  121. * @param {string} value the CSS color to apply the rotation on
  122. * @param {string} filter the CSS hue rotate filter declaration
  123. * @return {string} the rotated CSS color
  124. * @access private
  125. */
  126. rotateHue (value, filter) {
  127. let match = value.match(/rgb(a?)\((\d+), (\d+), (\d+)(, (\d+(\.\d+)?))?\)/)
  128. let [, , r, g, b, , a] = match
  129.  
  130. let [, hue] = filter.match(/hue-rotate\((\d+)deg\)/)
  131.  
  132. ;[r, g, b, a, hue] = [r, g, b, a, hue].map(Number)
  133. ;[r, g, b] = rotate(r, g, b, hue)
  134.  
  135. if (isNaN(a)) {
  136. return `rgb(${r}, ${g}, ${b})`
  137. } else {
  138. return `rgba(${r}, ${g}, ${b}, ${a})`
  139. }
  140. }
  141. }
  142.  
  143. // ## ## ######## ## ######## ######## ######## ######
  144. // ## ## ## ## ## ## ## ## ## ## ##
  145. // ## ## ## ## ## ## ## ## ## ##
  146. // ######### ###### ## ######## ###### ######## ######
  147. // ## ## ## ## ## ## ## ## ##
  148. // ## ## ## ## ## ## ## ## ## ##
  149. // ## ## ######## ######## ## ######## ## ## ######
  150.  
  151. /**
  152. * Computes the hue rotation on the provided `r`, `g` and `b` channels
  153. * by the amount of `angle`.
  154. *
  155. * @param {number} r the red channel of the color to rotate
  156. * @param {number} g the green channel of the color to rotate
  157. * @param {number} b the blue channel of the color to rotate
  158. * @param {number} angle the angle to rotate the hue with
  159. * @return {Array<number>} the rotated color channels
  160. * @access private
  161. */
  162. function rotate (r, g, b, angle) {
  163. let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
  164. const lumR = 0.2126
  165. const lumG = 0.7152
  166. const lumB = 0.0722
  167. const hueRotateR = 0.143
  168. const hueRotateG = 0.140
  169. const hueRotateB = 0.283
  170. const cos = Math.cos(angle * Math.PI / 180)
  171. const sin = Math.sin(angle * Math.PI / 180)
  172.  
  173. matrix[0] = lumR + (1 - lumR) * cos - (lumR * sin)
  174. matrix[1] = lumG - (lumG * cos) - (lumG * sin)
  175. matrix[2] = lumB - (lumB * cos) + (1 - lumB) * sin
  176. matrix[3] = lumR - (lumR * cos) + hueRotateR * sin
  177. matrix[4] = lumG + (1 - lumG) * cos + hueRotateG * sin
  178. matrix[5] = lumB - (lumB * cos) - (hueRotateB * sin)
  179. matrix[6] = lumR - (lumR * cos) - ((1 - lumR) * sin)
  180. matrix[7] = lumG - (lumG * cos) + lumG * sin
  181. matrix[8] = lumB + (1 - lumB) * cos + lumB * sin
  182.  
  183. return [
  184. clamp(matrix[0] * r + matrix[1] * g + matrix[2] * b),
  185. clamp(matrix[3] * r + matrix[4] * g + matrix[5] * b),
  186. clamp(matrix[6] * r + matrix[7] * g + matrix[8] * b)
  187. ]
  188.  
  189. function clamp (num) {
  190. return Math.ceil(Math.max(0, Math.min(255, num)))
  191. }
  192. }