從最近寫的一個圖表庫中單獨抽象出來了顏色類庫,功能包括HEX、RGB/RGBA以及HSL/HSLA各類色值的轉換以及顏色明暗變化。
在編寫的過程當中,涉及到了JS中的各類位運算符,對16進制色值的處理再也不是循環遍歷了。只對位運算符感興趣的建議直接閱讀目錄中的「HEX色值的快速轉換」。css
先上兩張圖,循環了1600個div,分別設置顏色的漸變和隨機。雖然如今css中對顏色的處理方法愈來愈豐富,但在一些場景——例如可視化圖表中咱們仍是須要用JS來控制顏色。vue
將各類格式的色值進行統一,方便操做,也確保展現效果一致。git
對顏色進行明暗處理,最明時爲白色(#fff),最暗時爲黑色(#000)。github
其中顏色格式包括:正則表達式
3位Hex值瀏覽器
6位Hex值babel
整數型RGBthis
百分比型RGBspa
整數型RGBAcode
百分比型RGBA
HSL
HSLA
常見的顏色命名,如black
要實現以上的功能,流程上應該包括:
經過正則表達式檢測顏色格式。
將顏色統一爲一種最易操做的格式。因爲咱們的操做主要爲明暗操做,那麼RGB/RGBA格式顯然是最方便的,所以將各類格式統一爲RGB/RGBA。
爲每一個格式化後的顏色添加「變明」、「變暗」兩個方法,並返回一個新的標準格式顏色對象,以便鏈式調用。
顏色對象還須要有一個輸出顏色字符串的方法,以便在全部操做完成後輸出最終的色值添加給對應的Dom。
注意,此類庫使用了部分ES6語法,如需轉化爲瀏覽器可直接使用的版本,可用babel進行轉換。
檢測格式時,主要依靠的是正則表達式,具體以下:
const reHex3 = /^#([0-9a-f]{3})$/ const reHex6 = /^#([0-9a-f]{6})$/ const reRgbInteger = /^rgb\(\s*([-+]?\d+)\s*,\s*([-+]?\d+)\s*,\s*([-+]?\d+)\s*\)$/ const reRgbPercent = /^rgb\(\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*\)$/ const reRgbaInteger = /^rgba\(\s*([-+]?\d+)\s*,\s*([-+]?\d+)\s*,\s*([-+]?\d+)\s*,\s*([-+]?\d+(?:\.\d+)?)\s*\)$/ const reRgbaPercent = /^rgba\(\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)\s*\)$/ const reHslPercent = /^hsl\(\s*([-+]?\d+(?:\.\d+)?)\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*\)$/ const reHslaPercent = /^hsla\(\s*([-+]?\d+(?:\.\d+)?)\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)%\s*,\s*([-+]?\d+(?:\.\d+)?)\s*\)$/
對於已命名的顏色,則構建了一個named對象,key爲顏色名稱,value則爲16進制色值,例如:
const named = { aliceblue: 0xf0f8ff, antiquewhite: 0xfaebd7, ... yellowgreen: 0x9acd32 } 經過named.hasOwnProperty方法來檢測輸入的字符串是不是已命名的顏色,若是是,則用其16進制色值替換。
實際上,我建立了3個class,分別爲Color、Rgb和Hsl。以上的顏色檢測均放在Color的format方法中,將格式化以後的顏色放入Color的f屬性裏,代碼以下:
class Color { constructor () { this.f = {} } format (str) { let m str = (str + '').trim().toLowerCase() if (reHex3.exec(str)) { m = parseInt(reHex3.exec(str)[1], 16) this.f = new Rgb((m >> 8 & 0xf) | (m >> 4 & 0x0f0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) } else if (reHex6.exec(str)) { m = parseInt(reHex6.exec(str)[1], 16) this.f = this.rgbn(m) } else if (reRgbInteger.exec(str)) { m = reRgbInteger.exec(str) this.f = new Rgb(m[1], m[2], m[3], 1) } else if (reRgbPercent.exec(str)) { m = reRgbPercent.exec(str) const r = 255 / 100 this.f = new Rgb(m[1] * r, m[2] * r, m[3] * r, 1) } else if (reRgbaInteger.exec(str)) { m = reRgbaInteger.exec(str) this.f = this.rgba(m[1], m[2], m[3], m[4]) } else if (reRgbaPercent.exec(str)) { m = reRgbaPercent.exec(str) const r = 255 / 100 this.f = this.rgba(m[1] * r, m[2] * r, m[3] * r, m[4]) } else if (reHslPercent.exec(str)) { m = reHslPercent.exec(str) this.f = this.hsla(m[1], m[2] / 100, m[3] / 100, 1) } else if (reHslaPercent.exec(str)) { m = reHslaPercent.exec(str) this.f = this.hsla(m[1], m[2] / 100, m[3] / 100, m[4]) } else if (named.hasOwnProperty(str)) { this.f = this.rgbn(named[str]) } else if (str === 'transparent') { this.f = new Rgb(NaN, NaN, NaN, 0) } else { this.f = null throw new Error('Invalid color format.') } return this.f } rgbn (n) { return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1) } rgba (r, g, b, a) { if (a <= 0) r = g = b = NaN return new Rgb(r, g, b, a) } hsla (h, s, l, a) { if (a <= 0) { h = s = l = NaN } else if (l <= 0 || l >= 1) { h = s = NaN } else if (s <= 0) { h = NaN } return new Hsl(h, s, l, a).rgb() } }
爲了方便讀者快速理解代碼,用了大量的if / else if,實際能夠用三元表達式替代,讓代碼更優雅緊湊。
經過閱讀Color類,能夠知道最終f屬性均爲一個 new Rgb
或 new Hsl
構造出來的對象,接下來就具體說說Color類中的這些位運算符起到了什麼做用。
HSL和RGB的轉換沒有什麼黑魔法,都是查Wiki以後寫的方法,大同小異,因此重點講講16進制色值是怎樣處理的。
網上資料中,大部分的HEX轉Rgb都是經過遍歷字符串,將HEX色值分隔,再轉化爲10進制數字。但在閱讀d3.js的源碼後,發現還有更巧妙的處理方法。
首先補充一下HEX色值的基本概念。HEX色值能夠爲3位或者6位,3位能夠理解爲一種簡寫,如#123
,實際等於#112233
。
而對於一個6位的HEX色值,如#112233
,在轉換爲RGB時,實際是每兩位對應RGB中的一個值,即十一、2二、33分別對應R、G、B。
>>
和&
首先以6位HEX色值爲例,咱們經過正則表達式取出其值後,parseInt(str, 16)轉化爲16進制數字,也能夠經過在前面加上'0x'來達到這一效果,目的都是告訴解析器,它是一個16進制的數。依然以#112233
爲例,具體看看代碼:
const m = parseInt('112233', 16) // 0x112233 // 分別獲取R、G、B的值 const r = m >> 16 & 0xff // 17 const g = m >> 8 & 0xff // 34 const b = m & 0xff // 51
那麼>>
和&
分別起什麼做用,爲何這樣一操做就能直接取出對應數值呢?
>>
是JS中的右移運算符,用於將數字的二進制右移n位。對於一個16進制的數字而言,每一位數字都對應4位2進制數字,如0x112233
的二進制就是0001 0001 0010 0010 0011 0011
。
所以要取出最左端11對應的10進制數字,只須要將其右移16位,剩下左起的8位便可。
那麼當咱們須要取中間的22和最右端的33時該怎麼辦呢?這就須要用到&
。&
是JS中位的與運算,提及來有點繞口,實際就是將兩端的值的二進制按位一一取與運算。
因此咱們實際看看取22和33時發生了什麼:
// 0x112233的二進制爲0001 0001 0010 0010 0011 0011 let n = 0x112233 >> 8 // 0001 0001 0010 0010 // 將n和0xff按位與運算,0xff的二進制爲1111 1111 n & 0xff // 0010 0010 也就是 0x22 n = 0x112233 & 0xff // 0011 0011 也就是 0x33
簡單的說,就是經過與0xff這個二進制最右端8均爲1的數與運算,從而取出目標數最右端的八位,並捨棄其他全部位數。
總的來講,就是先用>>
調整位置,再用&
篩選。
<<
和|
咱們接着處理3位HEX值,以#123
爲例,取出對應的R、G、B。
const m = parseInt('123', 16) // 0x123 const r = (m >> 8 & 0xf) | (m >> 4 & 0x0f0) // 17 const g = (m >> 4 & 0xf) | (m & 0xf0) // 34 const b = ((m & 0xf) << 4) | (m & 0xf) // 51
代碼中出現的|
是位的或運算符,機制和&
相相似。<<
則是和>>
對應的左移運算符。
一樣一步一步看看|
是怎麼起到做用的:
// 0x123的二進制爲0001 0010 0011 0x123 >> 8 & 0xf // 0001 0x123 >> 4 & 0x0f0 // 0001 0000 0001 | 0001 0000 // 0001 0001 也就是 0x11 0x123 >> 4 & 0xf // 0010 0x123 & 0xf0 // 0010 0000 0010 | 0010 0000 // 0010 0010 也就是 0x22 (0x123 & 0xf) << 4 // 0011 0000 0x123 & 0xf // 0011 0011 0000 | 0011 // 0011 0011 也就是 0x33
思路和6位時同樣,只是增長了<<
和|
,更靈活的操做各類位運算。
以後要作的主要就是一些HSL轉換、明暗變化以及各類錯誤處理,都是比較常規的作法,這裏很少作贅述,有興趣的能夠看看代碼:https://github.com/Yuyz0112/v...