lib/mixins/dom-styles-reader.js
'use babel'
import Mixin from 'mixto'
/**
* This mixin is used by the `CanvasDrawer` in `MinimapElement` to
* read the styles informations from the DOM to use when rendering
* the `Minimap`.
*/
export default class DOMStylesReader extends Mixin {
/**
* Returns the computed values for the given property and scope in the DOM.
*
* This function insert a dummy element in the DOM to compute
* its style, return the specified property, and clear the content of the
* dummy element.
*
* @param {Array<string>} scopes a list of classes reprensenting the scope
* to build
* @param {string} property the name of the style property to compute
* @param {boolean} [shadowRoot=true] whether to compute the style inside
* a shadow DOM or not
* @param {boolean} [cache=true] whether to cache the computed value or not
* @return {string} the computed property's value
*/
retrieveStyleFromDom (scopes, property, shadowRoot = true, cache = true) {
this.ensureCache()
let key = scopes.join(' ')
let cachedData = this.constructor.domStylesCache[key]
if (cache && (cachedData ? cachedData[property] : void 0) != null) {
return cachedData[property]
}
this.ensureDummyNodeExistence(shadowRoot)
if (!cachedData) {
this.constructor.domStylesCache[key] = cachedData = {}
}
let parent = this.dummyNode
for (let i = 0, len = scopes.length; i < len; i++) {
let scope = scopes[i]
let node = document.createElement('span')
node.className = scope.replace(/\.+/g, ' ')
if (parent != null) { parent.appendChild(node) }
parent = node
}
let style = window.getComputedStyle(parent)
let filter = style.getPropertyValue('-webkit-filter')
let value = style.getPropertyValue(property)
if (filter.indexOf('hue-rotate') > -1) {
value = this.rotateHue(value, filter)
}
if (value !== '') { cachedData[property] = value }
this.dummyNode.innerHTML = ''
return value
}
/**
* Creates a DOM node container for all the operations that need to read
* styles properties from DOM.
*
* @param {boolean} shadowRoot whether to create the dummy node in the shadow
* DOM or not
* @access private
*/
ensureDummyNodeExistence (shadowRoot) {
if (this.dummyNode == null) {
/**
* @access private
*/
this.dummyNode = document.createElement('span')
this.dummyNode.style.visibility = 'hidden'
}
this.getDummyDOMRoot(shadowRoot).appendChild(this.dummyNode)
}
/**
* Ensures the presence of the cache object in the class that received
* this mixin.
*
* @access private
*/
ensureCache () {
if (!this.constructor.domStylesCache) {
this.constructor.domStylesCache = {}
}
}
/**
* Invalidates the cache by emptying the cache object.
*/
invalidateDOMStylesCache () {
this.constructor.domStylesCache = {}
}
/**
* Invalidates the cache only for the first tokenization event.
*
* @access private
*/
invalidateIfFirstTokenization () {
if (this.constructor.hasTokenizedOnce) { return }
this.invalidateDOMStylesCache()
this.constructor.hasTokenizedOnce = true
}
/**
* Computes the output color of `value` with a rotated hue defined
* in `filter`.
*
* @param {string} value the CSS color to apply the rotation on
* @param {string} filter the CSS hue rotate filter declaration
* @return {string} the rotated CSS color
* @access private
*/
rotateHue (value, filter) {
let match = value.match(/rgb(a?)\((\d+), (\d+), (\d+)(, (\d+(\.\d+)?))?\)/)
let [, , r, g, b, , a] = match
let [, hue] = filter.match(/hue-rotate\((\d+)deg\)/)
;[r, g, b, a, hue] = [r, g, b, a, hue].map(Number)
;[r, g, b] = rotate(r, g, b, hue)
if (isNaN(a)) {
return `rgb(${r}, ${g}, ${b})`
} else {
return `rgba(${r}, ${g}, ${b}, ${a})`
}
}
}
// ## ## ######## ## ######## ######## ######## ######
// ## ## ## ## ## ## ## ## ## ## ##
// ## ## ## ## ## ## ## ## ## ##
// ######### ###### ## ######## ###### ######## ######
// ## ## ## ## ## ## ## ## ##
// ## ## ## ## ## ## ## ## ## ##
// ## ## ######## ######## ## ######## ## ## ######
/**
* Computes the hue rotation on the provided `r`, `g` and `b` channels
* by the amount of `angle`.
*
* @param {number} r the red channel of the color to rotate
* @param {number} g the green channel of the color to rotate
* @param {number} b the blue channel of the color to rotate
* @param {number} angle the angle to rotate the hue with
* @return {Array<number>} the rotated color channels
* @access private
*/
function rotate (r, g, b, angle) {
let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
const lumR = 0.2126
const lumG = 0.7152
const lumB = 0.0722
const hueRotateR = 0.143
const hueRotateG = 0.140
const hueRotateB = 0.283
const cos = Math.cos(angle * Math.PI / 180)
const sin = Math.sin(angle * Math.PI / 180)
matrix[0] = lumR + (1 - lumR) * cos - (lumR * sin)
matrix[1] = lumG - (lumG * cos) - (lumG * sin)
matrix[2] = lumB - (lumB * cos) + (1 - lumB) * sin
matrix[3] = lumR - (lumR * cos) + hueRotateR * sin
matrix[4] = lumG + (1 - lumG) * cos + hueRotateG * sin
matrix[5] = lumB - (lumB * cos) - (hueRotateB * sin)
matrix[6] = lumR - (lumR * cos) - ((1 - lumR) * sin)
matrix[7] = lumG - (lumG * cos) + lumG * sin
matrix[8] = lumB + (1 - lumB) * cos + lumB * sin
return [
clamp(matrix[0] * r + matrix[1] * g + matrix[2] * b),
clamp(matrix[3] * r + matrix[4] * g + matrix[5] * b),
clamp(matrix[6] * r + matrix[7] * g + matrix[8] * b)
]
function clamp (num) {
return Math.ceil(Math.max(0, Math.min(255, num)))
}
}