從零開(kāi)始實(shí)現(xiàn)一個(gè)顏色選擇器(原生JavaScript實(shí)現(xiàn))
作者:夕水
https://segmentfault.com/a/1190000040789940
準(zhǔn)備工作
項(xiàng)目目錄與文件創(chuàng)建
首先,我們無(wú)需搭建項(xiàng)目的環(huán)境,我們還是直接用最簡(jiǎn)單的方式,也就是引入的方式來(lái)創(chuàng)建這個(gè)項(xiàng)目,這樣也就方便了我們一邊編寫(xiě)一邊測(cè)試。創(chuàng)建一個(gè)空目錄,命名為ColorPicker,創(chuàng)建一個(gè)js文件,即color-picker.js,然后創(chuàng)建一個(gè)index.html文件以及創(chuàng)建一個(gè)樣式文件color-picker.css?,F(xiàn)在你應(yīng)該可以看到你的項(xiàng)目目錄是如下所示:
ColorPicker
│??index.html
│??color-picker.js
│??color-picker.css
在你的index.html中,初始化html文檔結(jié)構(gòu),然后引入這個(gè)color-picker.js文件,如下所示:
"en">
??
????"UTF-8"?/>
????"X-UA-Compatible"?content="IE=edge"?/>
????"viewport"?content="width=device-width,?initial-scale=1.0"?/>
????color-picker
????"stylesheet"?href="./color-picker.css"?/>
??
??
??
做好這些準(zhǔn)備工作之后,讓我們繼續(xù)下一步。
結(jié)構(gòu)與布局
模塊分析
我們通過(guò)如下一張圖來(lái)分析我們要實(shí)現(xiàn)的模塊,如下圖所示:

正如上圖所示,我們可以將一個(gè)顏色選擇器拆分成多個(gè)模塊,所以我們大致得到了一個(gè)結(jié)構(gòu)如下:
顏色色塊 顏色面板 色調(diào)柱 透明度柱 輸入框 清空與確定按鈕 預(yù)定義顏色元素列表
這樣一來(lái),我們可以清晰的看到整個(gè)顏色選擇器都有哪些模塊。我們目前只需要考慮開(kāi)發(fā)出基本的模塊功能,然后后續(xù)就在基礎(chǔ)上開(kāi)始進(jìn)行擴(kuò)展和完善。好的,讓我們繼續(xù)下一步,搭建頁(yè)面的基本結(jié)構(gòu)。
色塊模塊
通過(guò)分析,我們應(yīng)該知道,色塊分成兩種情況,第一種就是有顏色值時(shí),色塊應(yīng)該是一個(gè)背景色為該顏色值的左右箭頭。就像如下圖所示:

而無(wú)顏色值,我們的色塊應(yīng)該是如下圖所示:

如此一來(lái),我們就確定了色塊的結(jié)構(gòu)元素,如下:
"ew-color-picker-box">
??
??"ew-color-picker-arrow">
????"ew-color-picker-arrow-left">
??????"ew-color-picker-arrow-right">
??????
??????"ew-color-picker-no">×
????
??
這里我們肯定是通過(guò)一個(gè)顏色值來(lái)確定使用哪一個(gè)結(jié)構(gòu)的,這個(gè)后續(xù)我們?cè)僬f(shuō)。我們現(xiàn)在就先確定色塊的元素結(jié)構(gòu)應(yīng)該是如下這樣呢。當(dāng)然這里的類名也可以是自己隨便自定義。
tips:我這里是為了有自己的特色,所以加了
ew-前綴名。如果你自己使用自己自定義的類名,那么你后續(xù)編寫(xiě)樣式和操作 DOM 元素的時(shí)候需要注意,要去更改。
還有注意×它是HTML字符實(shí)體,我們只需要知道它最終會(huì)顯示為X就行了,這里不會(huì)去細(xì)講,欲了解更多 HTML 字符實(shí)體知識(shí),可以前往HTML 字符實(shí)體
查看。
接下來(lái),讓我們完成色塊的樣式編寫(xiě)。我們先完成最外層的盒子元素。可以看到,最外層的它會(huì)有一個(gè)自定義的寬高,然后就是一個(gè)邊框,其它的就沒(méi)有什么了,這樣一來(lái),我們就知道了該編寫(xiě)什么樣的CSS代碼。這里我們還是采用本身寫(xiě)好的樣式。我們做個(gè)記錄:
色塊盒子的邊框顏色為 #dcdee2色塊盒子的字體顏色為 #535353色塊盒子有 4px的圓角色塊盒子有上下 4px的內(nèi)間距,7px的左右內(nèi)間距色塊盒子有 14px的字體大小色塊盒子有 1.5的行高,注意沒(méi)有單位
tips:1.5 倍行高是一個(gè)相對(duì)值,它是根據(jù)瀏覽器設(shè)置的字體大小來(lái)決定的,例如瀏覽器字體大小為 16px,那么 1.5 倍行高就是 16px * 1.5 = 24px 的行高
看到以上幾點(diǎn)要求,我們應(yīng)該知道,我們要采用哪個(gè)CSS屬性來(lái)實(shí)現(xiàn),腦海中要有一個(gè)清晰的認(rèn)識(shí)。
.ew-color-picker-box?{
??/*?邊框顏色為#dcdee2?*/
??border:?1px?solid?#dcdee2;
??/*?邊框有4px的圓角?*/
??border-radius:?4px;
??/*?4px的上下內(nèi)間距,7px的左右內(nèi)間距?*/
??padding:?4px?7px;
}
最外層的盒子元素的樣式,我們已經(jīng)編寫(xiě)完成了,接下來(lái),我們開(kāi)始編寫(xiě)沒(méi)有顏色值的時(shí)候的一個(gè)樣式。實(shí)際上它和最外層的色塊盒子樣式差不多,唯一需要注意的就是,我們后續(xù)將通過(guò)js來(lái)設(shè)置它的寬高以及行高了。因?yàn)樗莿?dòng)態(tài)改變的,不過(guò)這里我們可以先固定一個(gè)值,然后后續(xù)再做更改。
.ew-color-picker-box?>?.ew-color-box-no?{
??width:?40px;
??height:?40px;
??font-size:?20px;
??line-height:?40px;
??color:?#5e535f;
??border:?1px?solid?#e2dfe2;
??border-radius:?2px;
}
接下來(lái)就是實(shí)現(xiàn)有顏色值的樣式了,這個(gè)要有一點(diǎn)難度,難點(diǎn)在于我們?nèi)绾稳?shí)現(xiàn)一個(gè)類似下拉框箭頭一樣的下箭頭。我們通過(guò)分析頁(yè)面結(jié)構(gòu)元素,不難看出,實(shí)際上我們這里的下箭頭很明顯是通過(guò)兩個(gè)元素來(lái)拼湊成的,也就是說(shuō)一個(gè)元素只是一根旋轉(zhuǎn)了 45deg 的橫線,同樣的道理,另一個(gè)元素?zé)o非是旋轉(zhuǎn)的方向相反罷了。并且我們可以看到這兩根橫線是垂直水平居中的,這里,我們肯定很快就想到了彈性盒子布局,只需要兩個(gè)屬性就可以讓元素垂直水平居中。即justify-content:center與align-items:center這兩個(gè)屬性。所以,經(jīng)過(guò)這樣一分析,我們這里的實(shí)現(xiàn)就不難了。
2D 坐標(biāo)系

3D 坐標(biāo)系

如下所示:
.ew-color-picker-box-arrow?{
??display:?flex;
??justify-content:?center;
??align-items:?center;
??width:?40px;
??height:?40px;
??margin:?auto;
??z-index:?3;
}
.ew-color-picker-box-arrow-left?{
??width:?12px;
??height:?1px;
??display:?inline-block;
??background-color:?#fff;
??position:?relative;
??transform:?rotate(45deg);
}
.ew-color-picker-box-arrow-right?{
??width:?12px;
??height:?1px;
??display:?inline-block;
??background-color:?#fff;
??position:?relative;
??transform:?rotate(-45deg);
??right:?3px;
}
如此一來(lái),色塊模塊的頁(yè)面結(jié)構(gòu)和樣式就這樣被我們完成了,讓我們繼續(xù)。
顏色面板
顏色面板也是整個(gè)顏色選擇器中最難的部分,現(xiàn)在我們來(lái)分析一下結(jié)構(gòu)。首先,我們可以看到,它有一個(gè)容器元素,這個(gè)容器元素有點(diǎn)陰影效果,背景色是白色。這里需要知道的一個(gè)知識(shí)點(diǎn)就是盒子模型,也就是box-sizing屬性,它有 2 個(gè)屬性值:content-box,border-box。事實(shí)上在實(shí)際開(kāi)發(fā)中,我們用到最多的是border-box。我們來(lái)看文檔box-sizing。
通過(guò)文檔描述,我們知道了這個(gè)屬性的意思。那么這里這個(gè)顏色面板容器元素的盒子模型我們就需要注意了,在這里,它是標(biāo)準(zhǔn)盒子模型,也就是我們只是單獨(dú)包含內(nèi)容的寬高就行了。因此,我們總結(jié)如下:
1px 的實(shí)線邊框#ebeeff 盒子模型為標(biāo)準(zhǔn)盒子模型 陰影效果文檔 7px 的內(nèi)邊距 5px 的圓角
tips:這里留一個(gè)懸念,為什么要使用標(biāo)準(zhǔn)盒子模型。
到此為止,我們的容器元素就分析完成了,接下來(lái)開(kāi)始編寫(xiě)結(jié)構(gòu)與樣式。
"ew-color-picker">
??
.ew-color-picker?{
??min-width:?320px;
??box-sizing:?content-box;
??border:?1px?solid?#ebeeff;
??box-shadow:?0?4px?15px?rgba(0,?0,?0,?0.2);
??border-radius:?5px;
??z-index:?10;
??padding:?7px;
??text-align:?left;
}
現(xiàn)在我們?cè)賮?lái)確定容器元素中都有哪些元素,首先是一個(gè)顏色面板,顏色面板又包含一個(gè)容器元素,我們可以看到,顏色面板很像是三種背景色疊加出來(lái)的效果,不用懷疑,大膽的說(shuō),是的沒(méi)錯(cuò),就是三種背景色疊加出來(lái)的,所以我們就需要一個(gè)容器元素,然后容器元素里面又包含 2 個(gè)面板元素,容器元素的背景色加上 2 個(gè)面板元素疊加出來(lái)就是這種效果。一個(gè)白色的背景加一個(gè)黑色的就能疊加看到我們想要的效果。
比如我們先來(lái)看看一個(gè)示例:
"panel">
??"white-panel">
??"black-panel">
.panel?{
??width:?280px;
??height:?180px;
??position:?relative;
??border:?1px?solid?#fff;
??background-color:?rgb(255,?166,?0);
}
.panel?>?div.white-panel,
.panel?>?div.black-panel?{
??position:?absolute;
??left:?0;
??right:?0;
??top:?0;
??bottom:?0;
}
.white-panel?{
??background:?linear-gradient(90deg,?#fff,?rgba(255,?255,?255,?0));
}
.black-panel?{
??background:?linear-gradient(0deg,?#000,?transparent);
}
這里可能又涉及到一個(gè)知識(shí)點(diǎn),那就是漸變顏色,這里就不做細(xì)講,感興趣的可查看文檔。
所以我們的結(jié)構(gòu)應(yīng)該是如下:
"ew-color-picker-content">
??"ew-color-picker-panel">
????"ew-color-picker-white-panel">
????"ew-color-picker-black-panel">
??
根據(jù)前面那個(gè)示例,我們很快就能寫(xiě)出這個(gè)顏色面板了,不過(guò)我們還少了一個(gè),也就是在顏色面板區(qū)域之內(nèi)的拖動(dòng)元素,或者我們可以稱之為游標(biāo)元素。
.ew-color-picker-panel?{
??width:?280px;
??height:?180px;
??position:?relative;
??border:?1px?solid?#fff;
??background-color:?rgb(255,?166,?0);
??cursor:?pointer;
}
.ew-color-picker-panel?>?div.ew-color-picker-white-panel,
.ew-color-picker-panel?>?div.ew-color-picker-black-panel?{
??position:?absolute;
??left:?0;
??right:?0;
??top:?0;
??bottom:?0;
}
.ew-color-picker-white-panel?{
??background:?linear-gradient(90deg,?#fff,?rgba(255,?255,?255,?0));
}
.ew-color-picker-black-panel?{
??background:?linear-gradient(0deg,?#000,?transparent);
}
好了,現(xiàn)在我可以回答之前那個(gè)留下的問(wèn)題了,為什么要使用標(biāo)準(zhǔn)盒子模型而不是 IE 標(biāo)準(zhǔn)盒子模型。這是因?yàn)檫@里我們會(huì)通過(guò) js 動(dòng)態(tài)去計(jì)算游標(biāo)元素拖動(dòng)的距離,如果是 IE 標(biāo)準(zhǔn)盒子模型,則會(huì)考慮邊框的大小以及間距的大小,這無(wú)疑給我們計(jì)算拖動(dòng)距離增加了難度,所以為了簡(jiǎn)便化,我們使用的是標(biāo)準(zhǔn)盒子模型。
現(xiàn)在我們?cè)賮?lái)加上這個(gè)游標(biāo)元素吧,因?yàn)樗窃陬伾姘鍍?nèi)動(dòng)態(tài)改變的,通常我們要讓一個(gè)元素在父元素當(dāng)中進(jìn)行移動(dòng),那么我們很明顯就想到了子元素使用絕對(duì)定位,父元素加一個(gè)除了靜態(tài)定位static以外的定位,通常我們用相對(duì)定位,這里也不例外。這也就是我們給.ew-color-picker-panel添加一個(gè)相對(duì)定位position: relative;的原因。
"ew-color-picker-content">
??"ew-color-picker-panel">
????
????"ew-color-picker-panel-cursor">
??
這里需要注意了,游標(biāo)元素設(shè)置的寬高會(huì)影響我們后續(xù)計(jì)算,所以在這里設(shè)置的寬高是多少,后續(xù)計(jì)算就要將它的寬高考慮在內(nèi),這個(gè)到后面會(huì)細(xì)講,現(xiàn)在,我們還是編寫(xiě)該元素的樣式吧。
.ew-color-picker-panel-cursor?{
??width:?4px;
??height:?4px;
??border-radius:?50%;
??position:?absolute;
??left:?100%;
??top:?0;
??transform:?translate(-4px,?-4px);
??box-shadow:?0?0?0?3px?#fff,?inset?0?0?2px?2px?rgb(0?0?0?/?40%),
????/*等價(jià)于rgba(0,0,0,0.4)*/?0?0?2px?3px?rgb(0?0?0?/?50%);?/*等價(jià)于rgba(0,0,0,0.5)*/
??cursor:?default;
}
游標(biāo)元素,我們看起來(lái)就像是一個(gè)小圓圈,所以我們給的寬高不是很多,只有 4px,既然是圓,我們都知道可以使用border-radius為50%即可以將一個(gè)元素變成圓。接下來(lái)就是陰影部分,這樣就實(shí)現(xiàn)了我們的小圓圈。當(dāng)然我們不一定非要實(shí)現(xiàn)這樣的效果,但是為了還原顏色選擇器本身,也方便后續(xù)的計(jì)算,所以我們還是采用原本的樣式。
色階柱
接下來(lái),我們來(lái)看一下色階柱也就是色調(diào)柱的實(shí)現(xiàn)??吹竭@個(gè)圖,我們應(yīng)該可以很清晰的分出色階柱包含了 2 個(gè)部分,第一個(gè)部分就是柱形部分,稱之為 bar,第二個(gè)部分就是拖動(dòng)滑塊部分,稱之為 thumb。然后我們外加一個(gè)容器元素用于包含色階柱和透明柱,所以我們可以確定色階柱的結(jié)構(gòu)如下:
"ew-color-slider?ew-is-vertical">
??"ew-color-slider-bar">
????"ew-color-slider-thumb">
??
然后我們來(lái)確定樣式的實(shí)現(xiàn),首先整個(gè)色階柱是垂直布局的,所以我們應(yīng)該知道它就是有一個(gè)固定寬度,然后高度等價(jià)于顏色面板的矩形,它的背景色通過(guò)一種漸變色來(lái)實(shí)現(xiàn),實(shí)際上就是紅橙黃綠青藍(lán)紫七種顏色的混合,也就類似彩虹。這每一種顏色都有不同的比例。其次我們還要知道滑塊部分是需要?jiǎng)討B(tài)拖動(dòng)的。在這里我們可以想象得到色階柱可以是水平或者垂直布局的,目前我們先實(shí)現(xiàn)垂直布局(為了區(qū)分給容器元素加一個(gè)類名 ew-is-vertical)。所以滑塊的動(dòng)態(tài)改變部分應(yīng)該是 top 值。現(xiàn)在我們來(lái)看樣式:
.ew-color-slider,
.ew-color-slider-bar?{
??position:?relative;
}
.ew-color-slider.ew-is-vertical?{
??width:?28px;
??height:?100%;
??cursor:?pointer;
??float:?right;
}
.ew-color-slider.ew-is-vertical?.ew-color-slider-bar?{
??width:?12px;
??height:?100%;
??float:?left;
??margin-left:?3px;
??background:?linear-gradient(
????180deg,
????#f00?0,
????#ff0?17%,
????#0f0?33%,
????#0ff?50%,
????#00f?67%,
????#f0f?83%,
????#f00
??);
}
.ew-color-slider-thumb?{
??background-color:?#fff;
??border-radius:?4px;
??position:?absolute;
??box-shadow:?0?0?2px?rgba(0,?0,?0,?0.5);
??border:?1px?solid?#dcdee2;
??left:?0;
??top:?0;
??box-sizing:?border-box;
??position:?absolute;
}
到目前為止,我們色階柱就算是實(shí)現(xiàn)了,接下來(lái)來(lái)看透明度柱的實(shí)現(xiàn)。
透明度柱
透明度柱的實(shí)現(xiàn)原理跟色階柱很相似,首先我們可以看到透明度柱會(huì)有一個(gè)透明的背景,這個(gè)背景很顯然是一個(gè)圖片,其次它還會(huì)有一個(gè)背景色條,取決于當(dāng)且色階柱處于哪種色調(diào),然后同樣還是與色階柱一樣有一個(gè)滑塊,同樣也是有垂直布局和水平布局,改變 top 值。所以我們得到結(jié)構(gòu)如下所示:
"ew-alpha-slider-bar">
??
??"ew-alpha-slider-wrapper">
??
??"ew-alpha-slider-bg">
??
??"ew-alpha-slider-thumb">
在這里,我們需要注意的一點(diǎn)就是背景色條的背景色是動(dòng)態(tài)改變,這將在后面會(huì)講到。背景色條,我們同樣是通過(guò)線性漸變來(lái)實(shí)現(xiàn)的。讓我們來(lái)看看樣式吧:
.ew-alpha-slider-bar?{
??width:?12px;
??height:?100%;
??float:?left;
??position:?relative;
}
.ew-alpha-slider-wrapper,
.ew-alpha-slider-bg?{
??position:?absolute;
??left:?0;
??top:?0;
??bottom:?0;
??right:?0;
}
.ew-alpha-slider-bar.ew-is-vertical?.ew-alpha-slider-bg?{
??/*?這里先暫時(shí)寫(xiě)死?*/
??background:?linear-gradient(
????to?top,
????rgba(255,?0,?0,?0)?0%,
????rgba(255,?0,?0)?100%
??);
}
.ew-alpha-slider-wrapper?{
??background:?url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==");
}
.ew-alpha-slider-thumb?{
??background-color:?#fff;
??border-radius:?4px;
??position:?absolute;
??box-shadow:?0?0?2px?rgba(0,?0,?0,?0.5);
??border:?1px?solid?#dcdee2;
??left:?0;
??top:?0;
??box-sizing:?border-box;
??position:?absolute;
}
好了,到目前為止,我們的透明度柱也就實(shí)現(xiàn)了,接下來(lái)我們來(lái)看輸入框的實(shí)現(xiàn)。
輸入框與按鈕
輸入框比較簡(jiǎn)單,我想沒(méi)什么好說(shuō)的,這個(gè)輸入框也可以自定義,它的結(jié)構(gòu)無(wú)非就是如下:
"ew-color-input"?/>
它和清空與確定按鈕元素排在一行,因此我們用一個(gè)容器元素來(lái)包裹它們,結(jié)構(gòu)應(yīng)該如下:
"ew-color-drop-container">
??"ew-color-input"?/>
??"ew-color-drop-btn-group">
????
????
??
然后樣式也沒(méi)有什么好分析的,都是一些基礎(chǔ)樣式,我們繼續(xù)編寫(xiě)代碼。如下:
.ew-color-drop-container?{
??margin-top:?6px;
??padding-top:?4px;
??min-height:?28px;
??border-top:?1px?solid?#cdcdcd;
??position:?relative;
}
.ew-color-input?{
??display:?inline-block;
??padding:?8px?12px;
??border:?1px?solid?#e9ebee;
??border-radius:?4px;
??outline:?none;
??width:?160px;
??height:?28px;
??line-height:?28px;
??border:?1px?solid?#dcdfe6;
??padding:?0?5px;
??-webkit-transition:?border-color?0.2s?cubic-bezier(0.175,?0.885,?0.32,?1.275);
??transition:?border-color?0.2s?cubic-bezier(0.175,?0.885,?0.32,?1.275);
??border-radius:?5px;
??background-color:?#fff;
}
.ew-color-drop-btn-group?{
??position:?absolute;
??right:?0;
??top:?5px;
}
.ew-color-drop-btn?{
??padding:?5px;
??font-size:?12px;
??border-radius:?3px;
??-webkit-transition:?0.1s;
??transition:?0.1s;
??font-weight:?500;
??margin:?0;
??white-space:?nowrap;
??color:?#606266;
??border:?1px?solid?#dcdfe6;
??letter-spacing:?1px;
??text-align:?center;
??cursor:?pointer;
}
.ew-color-clear?{
??color:?#4096ef;
??border-color:?transparent;
??background-color:?transparent;
??padding-left:?0;
??padding-right:?0;
}
.ew-color-clear:hover?{
??color:?#66b1ff;
}
.ew-color-sure?{
??margin-left:?10px;
}
.ew-color-sure?{
??border-color:?#4096ef;
??color:?#4096ef;
}
輸入框和按鈕我們就已經(jīng)完成了,接下來(lái)我們?cè)賮?lái)看預(yù)定義顏色元素呢。
預(yù)定義顏色
預(yù)定義顏色元素實(shí)現(xiàn)起來(lái)也比較簡(jiǎn)單,就是一個(gè)容器元素,然后包含多個(gè)子元素,可能稍微難一點(diǎn)的就是子元素的樣式我們分為四種情況,第一種就是默認(rèn)的樣式,第二種就是禁止點(diǎn)擊的樣式,除此之外,我們還加了一個(gè)顏色透明度之間的區(qū)別,然后最后就是選中樣式。不多說(shuō),我們可以先寫(xiě) 4 個(gè)子元素來(lái)分別代表四種情況的樣式。如下:
"ew-pre-define-color-container">
??"ew-pre-define-color"?tabindex="0">
??"ew-pre-define-color?ew-has-alpha"?tabindex="1">
??????class="ew-pre-define-color?ew-pre-define-color-disabled"
????tabindex="2"
??>
??????class="ew-pre-define-color?ew-pre-define-color-active"
????tabindex="3"
??>
接下來(lái),我們來(lái)看樣式的實(shí)現(xiàn):
.ew-pre-define-color-container?{
??width:?280px;
??font-size:?12px;
??margin-top:?8px;
}
.ew-pre-define-color-container::after?{
??content:?"";
??display:?table;
??height:?0;
??visibility:?hidden;
??clear:?both;
}
.ew-pre-define-color-container?.ew-pre-define-color?{
??margin:?0?0?8px?8px;
??width:?20px;
??height:?20px;
??border-radius:?4px;
??border:?1px?solid?#9b979b;
??cursor:?pointer;
??float:?left;
}
.ew-pre-define-color-container?.ew-pre-define-color:hover?{
??opacity:?0.8;
}
.ew-pre-define-color-active?{
??box-shadow:?0?0?3px?2px?#409eff;
}
.ew-pre-define-color:nth-child(10n?+?1)?{
??margin-left:?0;
}
.ew-pre-define-color.ew-has-alpha?{
??background:?url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==");
}
.ew-pre-define-color.ew-pre-define-color-disabled?{
??cursor:?not-allowed;
}
樣式和布局就到此結(jié)束了,接下來(lái)才是我們的重點(diǎn),也就是實(shí)現(xiàn)顏色選擇器的功能。
JavaScript
工具方法
首先用一個(gè)空對(duì)象來(lái)管理工具方法。如下:
const?util?=?Object.create(null);
然后有如下方法:
const?util?=?Object.create(null);
const?_toString?=?Object.prototype.toString;
let?addMethod?=?(instance,?method,?func)?=>?{
??instance.prototype[method]?=?func;
??return?instance;
};
["Number",?"String",?"Function",?"Undefined",?"Boolean"].forEach(
??(type)?=>?(util["is"?+?type]?=?(value)?=>?typeof?value?===?type.toLowerCase())
);
util.addMethod?=?addMethod;
["Object",?"Array",?"RegExp"].forEach(
??(type)?=>
????(util["isDeep"?+?type]?=?(value)?=>
??????_toString.call(value).slice(8,?-1).toLowerCase()?===?type.toLowerCase())
);
util.isShallowObject?=?(value)?=>
??typeof?value?===?"object"?&&?!util.isNull(value);
util["ewObjToArray"]?=?(value)?=>
??util.isShallowObject(value)???Array.prototype.slice.call(value)?:?value;
util.isNull?=?(value)?=>?value?===?null;
util.ewAssign?=?function?(target)?{
??if?(util.isNull(target))?return;
??const?_?=?Object(target);
??for?(let?j?=?1,?len?=?arguments.length;?j?????const?source?=?arguments[j];
????if?(source)?{
??????for?(let?key?in?source)?{
????????if?(Object.prototype.hasOwnProperty.call(source,?key))?{
??????????_[key]?=?source[key];
????????}
??????}
????}
??}
??return?_;
};
util.addClass?=?(el,?className)?=>?el.classList.add(className);
util.removeClass?=?(el,?className)?=>?el.classList.remove(className);
util.hasClass?=?(el,?className)?=>?{
??let?_hasClass?=?(value)?=>
????new?RegExp("?"?+?el.className?+?"?").test("?"?+?value?+?"?");
??if?(util.isDeepArray(className))?{
????return?className.some((name)?=>?_hasClass(name));
??}?else?{
????return?_hasClass(className);
??}
};
util["setCss"]?=?(el,?prop,?value)?=>?el.style.setProperty(prop,?value);
util.setSomeCss?=?(el,?propValue?=?[])?=>?{
??if?(propValue.length)?{
????propValue.forEach((p)?=>?util.setCss(el,?p.prop,?p.value));
??}
};
util.isDom?=?(el)?=>
??util.isShallowObject(HTMLElement)
??????el?instanceof?HTMLElement
????:?(el?&&
????????util.isShallowObject(el)?&&
????????el.nodeType?===?1?&&
????????util.isString(el.nodeName))?||
??????el?instanceof?HTMLCollection?||
??????el?instanceof?NodeList;
util.ewError?=?(value)?=>
??console.error("[ewColorPicker?warn]\n"?+?new?Error(value));
util.ewWarn?=?(value)?=>?console.warn("[ewColorPicker?warn]\n"?+?value);
util.deepCloneObjByJSON?=?(obj)?=>?JSON.parse(JSON.stringify(obj));
util.deepCloneObjByRecursion?=?function?f(obj)?{
??if?(!util.isShallowObject(obj))?return;
??let?cloneObj?=?util.isDeepArray(obj)???[]?:?{};
??for?(let?k?in?obj)?{
????cloneObj[k]?=?util.isShallowObject(obj[k])???f(obj[k])?:?obj[k];
??}
??return?cloneObj;
};
util.getCss?=?(el,?prop)?=>?window.getComputedStyle(el,?null)[prop];
util.$?=?(ident)?=>?{
??if?(!ident)?return?null;
??return?document[
????ident.indexOf("#")?>?-1???"querySelector"?:?"querySelectorAll"
??](ident);
};
util["on"]?=?(element,?type,?handler,?useCapture?=?false)?=>?{
??if?(element?&&?type?&&?handler)?{
????element.addEventListener(type,?handler,?useCapture);
??}
};
util["off"]?=?(element,?type,?handler,?useCapture?=?false)?=>?{
??if?(element?&&?type?&&?handler)?{
????element.removeEventListener(type,?handler,?useCapture);
??}
};
util["getRect"]?=?(el)?=>?el.getBoundingClientRect();
util["baseClickOutSide"]?=?(element,?isUnbind?=?true,?callback)?=>?{
??const?mouseHandler?=?(event)?=>?{
????const?rect?=?util.getRect(element);
????const?target?=?event.target;
????if?(!target)?return;
????const?targetRect?=?util.getRect(target);
????if?(
??????targetRect.x?>=?rect.x?&&
??????targetRect.y?>=?rect.y?&&
??????targetRect.width?<=?rect.width?&&
??????targetRect.height?<=?rect.height
????)
??????return;
????if?(util.isFunction(callback))?callback();
????if?(isUnbind)?{
??????//?延遲解除綁定
??????setTimeout(()?=>?{
????????util.off(document,?util.eventType[0],?mouseHandler);
??????},?0);
????}
??};
??util.on(document,?util.eventType[0],?mouseHandler);
};
util["clickOutSide"]?=?(context,?config,?callback)?=>?{
??const?mouseHandler?=?(event)?=>?{
????const?rect?=?util.getRect(context.$Dom.picker);
????let?boxRect?=?null;
????if?(config.hasBox)?{
??????boxRect?=?util.getRect(context.$Dom.box);
????}
????const?target?=?event.target;
????if?(!target)?return;
????const?targetRect?=?util.getRect(target);
????//?利用rect來(lái)判斷用戶點(diǎn)擊的地方是否在顏色選擇器面板區(qū)域之內(nèi)
????if?(config.hasBox)?{
??????if?(
????????targetRect.x?>=?rect.x?&&
????????targetRect.y?>=?rect.y?&&
????????targetRect.width?<=?rect.width
??????)
????????return;
??????//?如果點(diǎn)擊的是盒子元素
??????if?(
????????targetRect.x?>=?boxRect.x?&&
????????targetRect.y?>=?boxRect.y?&&
????????targetRect.width?<=?boxRect.width?&&
????????targetRect.height?<=?boxRect.height
??????)
????????return;
??????callback();
????}?else?{
??????if?(
????????targetRect.x?>=?rect.x?&&
????????targetRect.y?>=?rect.y?&&
????????targetRect.width?<=?rect.width?&&
????????targetRect.height?<=?rect.height
??????)
????????return;
??????callback();
????}
????setTimeout(()?=>?{
??????util.off(document,?util.eventType[0],?mouseHandler);
????},?0);
??};
??util.on(document,?util.eventType[0],?mouseHandler);
};
util["createUUID"]?=?()?=>
??(Math.random()?*?10000000).toString(16).substr(0,?4)?+
??"-"?+
??new?Date().getTime()?+
??"-"?+
??Math.random().toString().substr(2,?5);
util.removeAllSpace?=?(value)?=>?value.replace(/\s+/g,?"");
util.isJQDom?=?(dom)?=>
??typeof?window.jQuery?!==?"undefined"?&&?dom?instanceof?jQuery;
//the?event
util.eventType?=?navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)
????["touchstart",?"touchmove",?"touchend"]
??:?["mousedown",?"mousemove",?"mouseup"];
動(dòng)畫(huà)函數(shù)的封裝
const?animation?=?{};
function?TimerManager()?{
??this.timers?=?[];
??this.args?=?[];
??this.isTimerRun?=?false;
}
TimerManager.makeTimerManage?=?function?(element)?{
??const?elementTimerManage?=?element.TimerManage;
??if?(!elementTimerManage?||?elementTimerManage.constructor?!==?TimerManager)?{
????element.TimerManage?=?new?TimerManager();
??}
};
const?methods?=?[
??{
????method:?"add",
????func:?function?(timer,?args)?{
??????this.timers.push(timer);
??????this.args.push(args);
??????this.timerRun();
????},
??},
??{
????method:?"timerRun",
????func:?function?()?{
??????if?(!this.isTimerRun)?{
????????let?timer?=?this.timers.shift(),
??????????args?=?this.args.shift();
????????if?(timer?&&?args)?{
??????????this.isTimerRun?=?true;
??????????timer(args[0],?args[1]);
????????}
??????}
????},
??},
??{
????method:?"next",
????func:?function?()?{
??????this.isTimerRun?=?false;
??????this.timerRun();
????},
??},
];
methods.forEach((method)?=>
??util.addMethod(TimerManager,?method.method,?method.func)
);
function?runNext(element)?{
??const?elementTimerManage?=?element.TimerManage;
??if?(elementTimerManage?&&?elementTimerManage.constructor?===?TimerManager)?{
????elementTimerManage.next();
??}
}
function?registerMethods(type,?element,?time)?{
??let?transition?=?"";
??if?(type.indexOf("slide")?>?-1)?{
????transition?=?"height"?+?time?+?"?ms";
????util.setCss(element,?"overflow",?"hidden");
????upAndDown();
??}?else?{
????transition?=?"opacity"?+?time?+?"?ms";
????inAndOut();
??}
??util.setCss(element,?"transition",?transition);
??function?upAndDown()?{
????const?isDown?=?type.toLowerCase().indexOf("down")?>?-1;
????if?(isDown)?util.setCss(element,?"display",?"block");
????const?getPropValue?=?function?(item,?prop)?{
??????let?v?=?util.getCss(item,?prop);
??????return?util.removeAllSpace(v).length???parseInt(v)?:?Number(v);
????};
????const?elementChildHeight?=?[].reduce.call(
??????element.children,
??????(res,?item)?=>?{
????????res?+=
??????????item.offsetHeight?+
??????????getPropValue(item,?"margin-top")?+
??????????getPropValue(item,?"margin-bottom");
????????return?res;
??????},
??????0
????);
????let?totalHeight?=?Math.max(element.offsetHeight,?elementChildHeight?+?10);
????let?currentHeight?=?isDown???0?:?totalHeight;
????let?unit?=?totalHeight?/?(time?/?10);
????if?(isDown)?util.setCss(element,?"height",?"0px");
????let?timer?=?setInterval(()?=>?{
??????currentHeight?=?isDown???currentHeight?+?unit?:?currentHeight?-?unit;
??????util.setCss(element,?"height",?currentHeight?+?"px");
??????if?(currentHeight?>=?totalHeight?||?currentHeight?<=?0)?{
????????clearInterval(timer);
????????util.setCss(element,?"height",?totalHeight?+?"px");
????????runNext(element);
??????}
??????if?(!isDown?&&?currentHeight?<=?0)?{
????????util.setCss(element,?"display",?"none");
????????util.setCss(element,?"height",?"0");
??????}
????},?10);
??}
??function?inAndOut()?{
????const?isIn?=?type.toLowerCase().indexOf("in")?>?-1;
????let?timer?=?null;
????let?unit?=?(1?*?100)?/?(time?/?10);
????let?curAlpha?=?isIn???0?:?100;
????util.setSomeCss(element,?[
??????{
????????prop:?"display",
????????value:?isIn???"none"?:?"block",
??????},
??????{
????????prop:?"opacity",
????????value:?isIn???0?:?1,
??????},
????]);
????let?handleFade?=?function?()?{
??????curAlpha?=?isIn???curAlpha?+?unit?:?curAlpha?-?unit;
??????if?(element.style.display?===?"none"?&&?isIn)
????????util.setCss(element,?"display",?"block");
??????util.setCss(element,?"opacity",?(curAlpha?/?100).toFixed(2));
??????if?(curAlpha?>=?100?||?curAlpha?<=?0)?{
????????if?(timer)?clearTimeout(timer);
????????runNext(element);
????????if?(curAlpha?<=?0)?util.setCss(element,?"display",?"none");
????????util.setCss(element,?"opacity",?curAlpha?>=?100???1?:?0);
??????}?else?{
????????timer?=?setTimeout(handleFade,?10);
??????}
????};
????handleFade();
??}
}
["slideUp",?"slideDown",?"fadeIn",?"fadeOut"].forEach((method)?=>?{
??animation[method]?=?function?(element)?{
????TimerManager.makeTimerManage(element);
????element.TimerManage.add(function?(element,?time)?{
??????return?registerMethods(method,?element,?time);
????},?arguments);
??};
});
一些顏色操作的算法
const?colorRegExp?=?/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
//?RGB?color
const?colorRegRGB?=
??/[rR][gG][Bb][Aa]?[(]([\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),){2}[\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),?[\s]*(0.\d{1,2}|1|0)?[)]{1}/g;
//?RGBA?color
const?colorRegRGBA?=
??/^[rR][gG][Bb][Aa][(]([\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?)[\s]*,){3}[\s]*(1|1.0|0|0?.[0-9]{1,2})[\s]*[)]{1}$/;
//?hsl?color
const?colorRegHSL?=
??/^[hH][Ss][Ll][(]([\s]*(2[0-9][0-9]|360|3[0-5][0-9]|[01]?[0-9][0-9]?)[\s]*,)([\s]*((100|[0-9][0-9]?)%|0)[\s]*,)([\s]*((100|[0-9][0-9]?)%|0)[\s]*)[)]$/;
//?HSLA?color
const?colorRegHSLA?=
??/^[hH][Ss][Ll][Aa][(]([\s]*(2[0-9][0-9]|360|3[0-5][0-9]|[01]?[0-9][0-9]?)[\s]*,)([\s]*((100|[0-9][0-9]?)%|0)[\s]*,){2}([\s]*(1|1.0|0|0?.[0-9]{1,2})[\s]*)[)]$/;
/**
?*?hex?to?rgba
?*?@param?{*}?hex
?*?@param?{*}?alpha
?*/
function?colorHexToRgba(hex,?alpha)?{
??let?a?=?alpha?||?1,
????hColor?=?hex.toLowerCase(),
????hLen?=?hex.length,
????rgbaColor?=?[];
??if?(hex?&&?colorRegExp.test(hColor))?{
????//the?hex?length?may?be?4?or?7,contained?the?symbol?of?#
????if?(hLen?===?4)?{
??????let?hSixColor?=?"#";
??????for?(let?i?=?1;?i?????????let?sColor?=?hColor.slice(i,?i?+?1);
????????hSixColor?+=?sColor.concat(sColor);
??????}
??????hColor?=?hSixColor;
????}
????for?(let?j?=?1,?len?=?hColor.length;?j???????rgbaColor.push(parseInt("0X"?+?hColor.slice(j,?j?+?2),?16));
????}
????return?util.removeAllSpace("rgba("?+?rgbaColor.join(",")?+?","?+?a?+?")");
??}?else?{
????return?util.removeAllSpace(hColor);
??}
}
/**
?*?rgba?to?hex
?*?@param?{*}?rgba
?*/
function?colorRgbaToHex(rgba)?{
??const?hexObject?=?{?10:?"A",?11:?"B",?12:?"C",?13:?"D",?14:?"E",?15:?"F"?},
????hexColor?=?function?(value)?{
??????value?=?Math.min(Math.round(value),?255);
??????const?high?=?Math.floor(value?/?16),
????????low?=?value?%?16;
??????return?""?+?(hexObject[high]?||?high)?+?(hexObject[low]?||?low);
????};
??const?value?=?"#";
??if?(/rgba?/.test(rgba))?{
????let?values?=?rgba
????????.replace(/rgba?(/,?"")
????????.replace(/)/,?"")
????????.replace(/[\s+]/g,?"")
????????.split(","),
??????color?=?"";
????values.map((value,?index)?=>?{
??????if?(index?<=?2)?{
????????color?+=?hexColor(value);
??????}
????});
????return?util.removeAllSpace(value?+?color);
??}
}
/**
?*?hsva?to?rgba
?*?@param?{*}?hsva
?*?@param?{*}?alpha
?*/
function?colorHsvaToRgba(hsva,?alpha)?{
??let?r,
????g,
????b,
????a?=?hsva.a;?//rgba(r,g,b,a)
??let?h?=?hsva.h,
????s?=?(hsva.s?*?255)?/?100,
????v?=?(hsva.v?*?255)?/?100;?//hsv(h,s,v)
??if?(s?===?0)?{
????r?=?g?=?b?=?v;
??}?else?{
????let?t?=?v,
??????p?=?((255?-?s)?*?v)?/?255,
??????q?=?((t?-?p)?*?(h?%?60))?/?60;
????if?(h?===?360)?{
??????r?=?t;
??????g?=?b?=?0;
????}?else?if?(h?60)?{
??????r?=?t;
??????g?=?p?+?q;
??????b?=?p;
????}?else?if?(h?120)?{
??????r?=?t?-?q;
??????g?=?t;
??????b?=?p;
????}?else?if?(h?180)?{
??????r?=?p;
??????g?=?t;
??????b?=?p?+?q;
????}?else?if?(h?240)?{
??????r?=?p;
??????g?=?t?-?q;
??????b?=?t;
????}?else?if?(h?300)?{
??????r?=?p?+?q;
??????g?=?p;
??????b?=?t;
????}?else?if?(h?360)?{
??????r?=?t;
??????g?=?p;
??????b?=?t?-?q;
????}?else?{
??????r?=?g?=?b?=?0;
????}
??}
??if?(alpha?>=?0?||?alpha?<=?1)?a?=?alpha;
??return?util.removeAllSpace(
????"rgba("?+
??????Math.ceil(r)?+
??????","?+
??????Math.ceil(g)?+
??????","?+
??????Math.ceil(b)?+
??????","?+
??????a?+
??????")"
??);
}
/**
?*?hsla?to?rgba
?*?換算公式:https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4#%E4%BB%8EHSL%E5%88%B0RGB%E7%9A%84%E8%BD%AC%E6%8D%A2
?*?@param?{*}?hsla
?*/
function?colorHslaToRgba(hsla)?{
??let?h?=?hsla.h,
????s?=?hsla.s?/?100,
????l?=?hsla.l?/?100,
????a?=?hsla.a;
??let?r,?g,?b;
??if?(s?===?0)?{
????r?=?g?=?b?=?l;
??}?else?{
????let?compareRGB?=?(p,?q,?t)?=>?{
??????if?(t?>?1)?t?=?t?-?1;
??????if?(t?0)?t?=?t?+?1;
??????if?(t?1?/?6)?return?p?+?(q?-?p)?*?6?*?t;
??????if?(t?1?/?2)?return?q;
??????if?(t?2?/?3)?return?p?+?(q?-?p)?*?6?*?(2?/?3?-?t);
??????return?p;
????};
????let?q?=?l?>=?0.5???l?+?s?-?l?*?s?:?l?*?(1?+?s),
??????p?=?2?*?l?-?q,
??????k?=?h?/?360;
????r?=?compareRGB(p,?q,?k?+?1?/?3);
????g?=?compareRGB(p,?q,?k);
????b?=?compareRGB(p,?q,?k?-?1?/?3);
??}
??return?util.removeAllSpace(
????`rgba(${Math.ceil(r?*?255)},${Math.ceil(g?*?255)},${Math.ceil(
??????b?*?255
????)},${a})`
??);
}
/**
?*?rgba?to?hsla
?*?換算公式:https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4#%E4%BB%8EHSL%E5%88%B0RGB%E7%9A%84%E8%BD%AC%E6%8D%A2
?*?@param?{*}?rgba
?*/
function?colorRgbaToHsla(rgba)?{
??const?rgbaArr?=?rgba
????.slice(rgba.indexOf("(")?+?1,?rgba.lastIndexOf(")"))
????.split(",");
??let?a?=?rgbaArr.length?4???1?:?Number(rgbaArr[3]);
??let?r?=?parseInt(rgbaArr[0])?/?255,
????g?=?parseInt(rgbaArr[1])?/?255,
????b?=?parseInt(rgbaArr[2])?/?255;
??let?max?=?Math.max(r,?g,?b),
????min?=?Math.min(r,?g,?b);
??let?h,
????s,
????l?=?(max?+?min)?/?2;
??if?(max?===?min)?{
????h?=?s?=?0;
??}?else?{
????const?d?=?max?-?min;
????s?=?l?>?0.5???d?/?(2?-?max?-?min)?:?d?/?(max?+?min);
????switch?(max)?{
??????case?r:
????????h?=?(g?-?b)?/?d?+?(g?>=?b???0?:?6);
????????break;
??????case?g:
????????h?=?(b?-?r)?/?d?+?2;
????????break;
??????case?b:
????????h?=?(r?-?g)?/?d?+?4;
????????break;
????}
??}
??return?{
????colorStr:?util.removeAllSpace(
??????"hsla("?+
????????Math.ceil(h?*?60)?+
????????","?+
????????Math.ceil(s?*?100)?+
????????"%,"?+
????????Math.ceil(l?*?100)?+
????????"%,"?+
????????a?+
????????")"
????),
????colorObj:?{
??????h,
??????s,
??????l,
??????a,
????},
??};
}
/**
?*?rgba?to?hsva
?*?@param?{*}?rgba
?*/
function?colorRgbaToHsva(rgba)?{
??const?rgbaArr?=?rgba
????.slice(rgba.indexOf("(")?+?1,?rgba.lastIndexOf(")"))
????.split(",");
??let?a?=?rgbaArr.length?4???1?:?Number(rgbaArr[3]);
??let?r?=?parseInt(rgbaArr[0])?/?255,
????g?=?parseInt(rgbaArr[1])?/?255,
????b?=?parseInt(rgbaArr[2])?/?255;
??let?h,?s,?v;
??let?min?=?Math.min(r,?g,?b);
??let?max?=?(v?=?Math.max(r,?g,?b));
??let?diff?=?max?-?min;
??if?(max?===?0)?{
????s?=?0;
??}?else?{
????s?=?1?-?min?/?max;
??}
??if?(max?===?min)?{
????h?=?0;
??}?else?{
????switch?(max)?{
??????case?r:
????????h?=?(g?-?b)?/?diff?+?(g?????????break;
??????case?g:
????????h?=?2.0?+?(b?-?r)?/?diff;
????????break;
??????case?b:
????????h?=?4.0?+?(r?-?g)?/?diff;
????????break;
????}
????h?=?h?*?60;
??}
??s?=?s?*?100;
??v?=?v?*?100;
??return?{
????h,
????s,
????v,
????a,
??};
}
/*
?*?任意色值(甚至是CSS顏色關(guān)鍵字)轉(zhuǎn)換為RGBA顏色的方法
?*?此方法IE9+瀏覽器支持,基于DOM特性實(shí)現(xiàn)
?*?@param?{*}?color
?*/
function?colorToRgba(color)?{
??const?div?=?document.createElement("div");
??util.setCss(div,?"background-color",?color);
??document.body.appendChild(div);
??const?c?=?util.getCss(div,?"background-color");
??document.body.removeChild(div);
??let?isAlpha?=?c.match(/,/g)?&&?c.match(/,/g).length?>?2;
??let?result?=?isAlpha
??????c
????:?c.slice(0,?2)?+?"ba"?+?c.slice(3,?c.length?-?1)?+?",?1)";
??return?util.removeAllSpace(result);
}
/**
?*?判斷是否是合格的顏色值
?*?@param?{*}?color
?*/
function?isValidColor(color)?{
??//?https://developer.mozilla.org/zh-CN/docs/Web/CSS/color_value#%E8%89%B2%E5%BD%A9%E5%85%B3%E9%94%AE%E5%AD%97
??let?isTransparent?=?color?===?"transparent";
??return?(
????colorRegExp.test(color)?||
????colorRegRGB.test(color)?||
????colorRegRGBA.test(color)?||
????colorRegHSL.test(color)?||
????colorRegHSLA.test(color)?||
????(colorToRgba(color)?!==?"rgba(0,0,0,0)"?&&?!isTransparent)?||
????isTransparent
??);
}
/**
?*
?*?@param?{*}?color
?*?@returns
?*/
function?isAlphaColor(color)?{
??return?(
????colorRegRGB.test(color)?||
????colorRegRGBA.test(color)?||
????colorRegHSL.test(color)?||
????colorRegHSLA.test(color)
??);
}
工具方法這些我們已經(jīng)完成了,接下來(lái)就是正式完成我們的主線功能邏輯了。
構(gòu)造函數(shù)的定義
首先當(dāng)然是完成我們的構(gòu)造函數(shù)呢,我們把一個(gè)顏色選擇器看做是一個(gè)構(gòu)造實(shí)例,也因此,我們創(chuàng)建一個(gè)構(gòu)造函數(shù)。
function?ewColorPicker(options){
???//主要邏輯
}
好的,接下來(lái),讓我們完成第一步,校驗(yàn)用戶傳入的參數(shù),我們分為2種情況,第一種是如果用戶傳入的是一個(gè)DOM元素字符串或者是一個(gè)DOM元素,那么我們就要定義一個(gè)默認(rèn)的配置對(duì)象,如果用戶傳入的是一個(gè)自定義的對(duì)象,那么我們將不采取默認(rèn)對(duì)象。在校驗(yàn)之前,我們先思考一下可能需要處理的錯(cuò)誤情況,也就是說(shuō)假如用戶傳入的參數(shù)不符合規(guī)則,我們是不是需要返回一些錯(cuò)誤提示給用戶知道,現(xiàn)在讓我們來(lái)定義一下這些錯(cuò)誤規(guī)則吧。如下所示:
const?NOT_DOM_ELEMENTS?=?['html','head','meta','title','link','style','script','body'];
const?ERROR_VARIABLE?=?{
????DOM_OBJECT_ERROR:'can?not?find?the?element?by?el?property,make?sure?to?pass?a?correct?value!',
????DOM_ERROR:'can?not?find?the?element,make?sure?to?pass?a?correct?param!',
????CONFIG_SIZE_ERROR:'the?value?must?be?a?string?which?is?one?of?the?normal,medium,small,mini,or?must?be?an?object?and?need?to?contain?width?or?height?property!',
????DOM_NOT_ERROR:'Do?not?pass?these?elements:?'?+?NOT_DOM_ELEMENTS.join(',')?+?'?as?a?param,pass?the?correct?element?such?as?div!',
????PREDEFINE_COLOR_ERROR:'"predefineColor"?is?a?array?that?is?need?to?contain?color?value!',
????CONSTRUCTOR_ERROR:'ewColorPicker?is?a?constructor?and?should?be?called?with?the?new?keyword!',
????DEFAULT_COLOR_ERROR:'the?"defaultColor"?is?not?an?invalid?color,make?sure?to?use?the?correct?color!'
};
這些校驗(yàn)錯(cuò)誤都是常量,不允許被修改的,所以我們用大寫(xiě)字母來(lái)表示。接下來(lái)我們就需要在構(gòu)造函數(shù)里做一個(gè)校驗(yàn)了。
配置屬性的定義與校驗(yàn)
1.校驗(yàn)是否是實(shí)例化
判斷new.target就可以了,如下所示:
if(util.isUndefined(new.target))return?ewError(ERROR_VARIABLE.CONSTRUCTOR_ERROR);
2.定義一個(gè)函數(shù)startInit,在這個(gè)函數(shù)里對(duì)具體的屬性做判斷。如下所示:
function?startInit(context,options){
??let?initOptions?=?initConfig(config);
????if(!initOptions)return;
????//?緩存配置對(duì)象屬性
????context.config?=?initOptions.config;
????//定義私有屬性
????context._private?=?{
????????boxSize:?{
????????????b_width:?null,
????????????b_height:?null
????????},
????????pickerFlag:?false,
????????colorValue:?"",
????};
????//?在初始化之前所作的操作
????context.beforeInit(initOptions.element,initOptions.config,initOptions.error);
}
接下來(lái),我們來(lái)看initConfig函數(shù),如下所示:
export?function?initConfig(config){
????//?默認(rèn)的配置對(duì)象屬性?
????const?defaultConfig?=?{?...colorPickerConfig?};
????let?element,error,mergeConfig?=?null;
????//如果第二個(gè)參數(shù)傳的是字符串,或DOM對(duì)象,則初始化默認(rèn)的配置
????if?(util.isString(config)?||?util.isDom(config)?||?util.isJQDom(config))?{
????????mergeConfig?=?defaultConfig;
????????element?=?util.isJQDom(config)???config.get(0)?:?config;
????????error?=?ERROR_VARIABLE.DOM_ERROR;
????}?//如果是對(duì)象,則自定義配置,自定義配置選項(xiàng)如下:
????else?if?(util.isDeepObject(config)?&&?(util.isString(config.el)?||?util.isDom(config.el)?||?util.isJQDom(config.el)))?{
????????mergeConfig?=?util.ewAssign(defaultConfig,?config);
????????element?=?util.isJQDom(config.el)???config.el.get(0)?:?config.el;
????????error?=?ERROR_VARIABLE.DOM_OBJECT_ERROR;
????}?else?{
????????if(util.isDeepObject(config)){
????????????error?=?ERROR_VARIABLE.DOM_OBJECT_ERROR;
????????}else{
????????????error?=?ERROR_VARIABLE.DOM_ERROR;
????????}
????}
????return?{
????????element,
????????config:mergeConfig,
????????error
????}
}
然后我們來(lái)看看默認(rèn)的配置對(duì)象屬性:
export?const?emptyFun?=?function?()?{?};
const?baseDefaultConfig?=?{
????alpha:?false,
????size:?"normal",
????predefineColor:?[],
????disabled:?false,
????defaultColor:?"",
????pickerAnimation:?"height",
????pickerAnimationTime:200,
????sure:?emptyFun,
????clear:?emptyFun,
????togglePicker:?emptyFun,
????changeColor:?emptyFun,
????isClickOutside:?true,
}
接下來(lái),我們來(lái)看beforeInit函數(shù),如下所示:
function?beforeInit(element,?config,?errorText)?{
????let?ele?=?util.isDom(element)???element?:?util.isString(element)???util.$(element)?:?util.isJQDom(element)???element.get(0)?:?null;
????if?(!ele)?return?util.ewError(errorText);
????ele?=?ele.length???ele[0]?:?ele;
????if?(!ele.tagName)?return?util.ewError(errorText);
????if?(!isNotDom(ele))?{
????????if(!this._color_picker_uid){
????????????this._color_picker_uid?=?util.createUUID();
????????}
????????this.init(ele,?config);
????}
}
其中,isNotDom方法,我們先定義好:
const?isNotDom?=?ele?=>?{
????if?(NOT_DOM_ELEMENTS.indexOf(ele.tagName.toLowerCase())?>?-1)?{
????????util.ewError(ERROR_VARIABLE.DOM_NOT_ERROR);
????????return?true;
????}
????return?false;
}
最后,我們來(lái)看init函數(shù),如下所示:
function?init(element,?config)?{
????let?b_width,?b_height;
????//自定義顏色選擇器的類型
????if?(util.isString(config.size))?{
????????switch?(config.size)?{
????????????case?'normal':
????????????????b_width?=?b_height?=?'40px';
????????????????break;
????????????case?'medium':
????????????????b_width?=?b_height?=?'36px';
????????????????break;
????????????case?'small':
????????????????b_width?=?b_height?=?'32px';
????????????????break;
????????????case?'mini':
????????????????b_width?=?b_height?=?'28px';
????????????????break;
????????????default:
????????????????b_width?=?b_height?=?'40px';
????????????????break;
????????}
????}?else?if?(util.isDeepObject(config.size))?{
????????b_width?=?config.size.width?&&?(util.isNumber(config.size.width)?||?util.isString(config.size.width))???(parseInt(config.size.width)?<=?25???25?:??parseInt(config.size.width))+?'px'?:?'40px';
????????b_height?=?config.size.height?&&?(util.isNumber(config.size.height)?||?util.isString(config.size.height))???(parseInt(config.size.height)?<=?25???25?:?parseInt(config.size.height))?+?'px'?:?'40px';
????}?else?{
????????return?util.ewError(ERROR_VARIABLE.CONFIG_SIZE_ERROR);
????}
????this._private.boxSize.b_width?=?b_width;
????this._private.boxSize.b_height?=?b_height;
????//渲染選擇器
????this.render(element,?config);
}
如此一來(lái),我們的初始化的工作才算是完成,回顧一下,我們?cè)诔跏蓟臅r(shí)候做了哪些操作。我總結(jié)如下:
定義了一些錯(cuò)誤的常量,用于提示。 驗(yàn)證用戶傳入的參數(shù),分為2種情況,第一種是字符串或者DOM元素,第二種是自定義對(duì)象,其中必須指定el屬性為一個(gè)DOM元素。 定義了默認(rèn)配置對(duì)象,定義了一些私有變量。 對(duì)色塊盒子的大小做了一次規(guī)范化。
接下來(lái),就是我們實(shí)際渲染一個(gè)顏色選擇器的渲染函數(shù),即render函數(shù)。
render函數(shù)
render函數(shù)的核心思路非常的簡(jiǎn)單,實(shí)際上就是創(chuàng)建一堆元素,然后添加到元素當(dāng)中去。只不過(guò)我們需要注意幾點(diǎn),例如預(yù)定義顏色數(shù)組,默認(rèn)顏色值,以及色塊盒子的大小,還有就是alpha柱的顯隱。如下所示:
ewColorPicker.prototype.render?=?function(element,config){
????let?predefineColorHTML?=?'',
????????alphaBar?=?'',
????????hueBar?=?'',
????????predefineHTML?=?'',
????????boxDisabledClassName?=?'',
????????boxBackground?=?'',
????????boxHTML?=?'',
????????clearHTML?=?'',
????????sureHTML?=?'',
????????inputHTML?=?'',
????????btnGroupHTML?=?'',
????????dropHTML?=?'',
????????openChangeColorModeHTML?=?'',
????????openChangeColorModeLabelHTML?=?'',
????????horizontalSliderHTML?=?'',
????????verticalSliderHTML?=?'';
????const?p_c?=?config.predefineColor;
????if?(!util.isDeepArray(p_c))?return?util.ewError(ERROR_VARIABLE.PREDEFINE_COLOR_ERROR);
????if?(p_c.length)?{
????????p_c.map((color,index)?=>?{
????????????let?isValidColorString?=?util.isString(color)?&&?isValidColor(color);
????????????let?isValidColorObj?=?util.isDeepObject(color)?&&?color.hasOwnProperty('color')?&&?isValidColor(color.color);
????????????let?renderColor?=?isValidColorString???color?:?isValidColorObj???color.color?:?'';
????????????let?renderDisabled?=?isValidColorObj???setPredefineDisabled(color.disabled)?:?'';
????????????predefineColorHTML?+=?`
????????????"ew-pre-define-color${hasAlpha(renderColor)}${renderDisabled}"?tabindex=${index}>
????????????????"ew-pre-define-color-item"?style="background-color:${renderColor};">
????????????`;
????????})
????};
????//打開(kāi)顏色選擇器的方框
????const?colorBox?=?config.defaultColor???`"ew-color-picker-arrow"?style="width:${this._private.boxSize.b_width};height:${this._private.boxSize.b_height};">
????????"ew-color-picker-arrow-left">
????????"ew-color-picker-arrow-right">
????`?:?`"ew-color-picker-no"?style="width:${this._private.boxSize.b_width};height:${this._private.boxSize.b_height};line-height:${this._private.boxSize.b_height};">×`;
????//透明度
????if?(config.alpha)?{
????????alphaBar?=?`"ew-alpha-slider-bar">
????????????"ew-alpha-slider-wrapper">
????????????"ew-alpha-slider-bg">
????????????"ew-alpha-slider-thumb">
????????`;
????}
????//?hue
????if?(config.hue)?{
????????hueBar?=?`"ew-color-slider-bar">"ew-color-slider-thumb">`;
????}
????if?(predefineColorHTML)?{
????????predefineHTML?=?`"ew-pre-define-color-container">${predefineColorHTML}`;
????}
????if?(config.disabled?||?config.boxDisabled)?boxDisabledClassName?=?'ew-color-picker-box-disabled';
????if?(config.defaultColor){
????????if(!isValidColor(config.defaultColor)){
????????????return?util.ewError(ERROR_VARIABLE.DEFAULT_COLOR_ERROR)
????????}else{
????????????config.defaultColor?=?colorToRgba(config.defaultColor);
????????}
????};
????this._private.color?=?config.defaultColor;
????if?(!config.disabled?&&?this._private.color)?boxBackground?=?`background:${this._private.color}`;
????//?盒子樣式
????const?boxStyle?=?`width:${this._private.boxSize.b_width};height:${this._private.boxSize.b_height};${boxBackground}`;
????if?(config.hasBox)?{
????????boxHTML?=?`"ew-color-picker-box?${boxDisabledClassName}"?tabIndex="0"?style="${boxStyle}">${colorBox}`;
????}
????if?(config.hasClear)?{
????????clearHTML?=?``;
????}
????if?(config.hasSure)?{
????????sureHTML?=?``;
????}
????if?(config.hasClear?||?config.hasSure)?{
????????btnGroupHTML?=?`"ew-color-drop-btn-group">${clearHTML}${sureHTML}`;
????}
????if?(config.hasColorInput)?{
????????inputHTML?=?'';
????}
????if?(config.openChangeColorMode)?{
????????if?(!config.alpha?||?!config.hue)?return?util.ewError(ERROR_VARIABLE.COLOR_MODE_ERROR);
????????openChangeColorModeHTML?=?`"ew-color-mode-container">
????????"ew-color-mode-up">
????????"ew-color-mode-down">
????????`;
????????openChangeColorModeLabelHTML?=?``;
????}
????if?(config.hasColorInput?||?config.hasClear?||?config.hasSure)?{
????????dropHTML?=?config.openChangeColorMode???`"ew-color-drop-container?ew-has-mode-container">
????????${openChangeColorModeLabelHTML}${inputHTML}${openChangeColorModeHTML}
????????"ew-color-drop-container">
????????${btnGroupHTML}
????????`?:?`"ew-color-drop-container">
????????${inputHTML}${btnGroupHTML}
????????`;
????}
????this.isAlphaHorizontal?=?config.alphaDirection?===?'horizontal';
????this.isHueHorizontal?=?config.hueDirection?===?'horizontal';
????if(this.isAlphaHorizontal?&&?this.isHueHorizontal){
????????horizontalSliderHTML?=?hueBar?+?alphaBar;
????}else?if(!this.isAlphaHorizontal?&&?!this.isHueHorizontal){
????????verticalSliderHTML?=?alphaBar?+?hueBar;
????}else{
????????if(this.isHueHorizontal){
????????????horizontalSliderHTML?=?hueBar;
????????????verticalSliderHTML?=?alphaBar;
????????}?else{
????????????horizontalSliderHTML?=?alphaBar;
????????????verticalSliderHTML?=?hueBar;
????????}
????}
????if(horizontalSliderHTML){
????????horizontalSliderHTML?=?`"ew-color-slider?ew-is-horizontal">${?horizontalSliderHTML?}`
????}
????if(verticalSliderHTML){
????????verticalSliderHTML?=?`"ew-color-slider?ew-is-vertical">${?verticalSliderHTML?}`;
????}
????//顏色選擇器
????const?html?=?`${boxHTML}
????????"ew-color-picker">
????????????"ew-color-picker-content">
????????????????${?verticalSliderHTML?}
????????????????"ew-color-panel"?style="background:red;">
????????????????????"ew-color-white-panel">
????????????????????"ew-color-black-panel">
????????????????????"ew-color-cursor">
????????????????
????????????
????????????${?horizontalSliderHTML?}
????????????${dropHTML}
????????????${predefineHTML}
????????`;
????element.setAttribute("color-picker-id",this._color_picker_uid);
????element.innerHTML?=?`"ew-color-picker-container">${?html?}`;
????this.startMain(element,?config);
}
startMain函數(shù)
接下來(lái),我們來(lái)看看我們要實(shí)現(xiàn)哪些邏輯。首先我們需要確定一個(gè)初始值的顏色對(duì)象,用hsva來(lái)表示,我們創(chuàng)建一個(gè)initColor函數(shù),代碼如下所示:
function?initColor(context,?config)?{
????if?(config.defaultColor)?{
????????context.hsvaColor?=?colorRegRGBA.test(config.defaultColor)???colorRgbaToHsva(config.defaultColor)?:?colorRgbaToHsva(colorToRgba(config.defaultColor));
????}?else?{
????????context.hsvaColor?=?{
????????????h:?0,
????????????s:?100,
????????????v:?100,
????????????a:?1
????????};
????}
}
這是我們要實(shí)現(xiàn)的第一個(gè)邏輯,也就是初始化顏色值,這個(gè)顏色值對(duì)象將貫穿整個(gè)顏色選擇器實(shí)例,所有的邏輯更改也會(huì)圍繞它展開(kāi)。接下來(lái),我們?cè)賰?nèi)部存儲(chǔ)一些DOM元素或者一些私有對(duì)象屬性以及用戶傳入的配置對(duì)象,這樣可以方便我們之后操作。
現(xiàn)在我們?cè)賮?lái)分析一下,我們可以大致得到主要的邏輯有:
初始化一些后續(xù)需要操作的DOM元素與顏色值以及面板的left與top偏移 預(yù)定義顏色邏輯 初始化顏色面板的動(dòng)畫(huà)邏輯 色塊盒子的處理邏輯 輸入框邏輯 禁用邏輯 點(diǎn)擊目標(biāo)區(qū)域之外關(guān)閉顏色面板的邏輯 清空按鈕與確定按鈕的邏輯 顏色面板的點(diǎn)擊邏輯與顏色面板的元素拖拽邏輯
我們接下來(lái)將圍繞這幾種邏輯一起展開(kāi)。如下所示:
????//?初始化邏輯
????let?scope?=?this;
????this.$Dom?=?Object.create(null);
????this.$Dom.rootElement?=?ele;
????this.$Dom.picker?=?getELByClass(ele,?'ew-color-picker');
????this.$Dom.pickerPanel?=?getELByClass(ele,?'ew-color-panel');
????this.$Dom.pickerCursor?=?getELByClass(ele,?'ew-color-cursor');
????this.$Dom.verticalSlider?=?getELByClass(ele,?'ew-is-vertical');
????//?清空按鈕邏輯
????this.$Dom.pickerClear?=?getELByClass(ele,?'ew-color-clear');
????this.$Dom.hueBar?=?getELByClass(ele,?'ew-color-slider-bar');
????this.$Dom.hueThumb?=?getELByClass(ele,?'ew-color-slider-thumb');
????this.$Dom.preDefineItem?=?getELByClass(ele,?'ew-pre-define-color',?true);
????this.$Dom.box?=?getELByClass(ele,?'ew-color-picker-box');
????//?輸入框邏輯
????this.$Dom.pickerInput?=?getELByClass(ele,?'ew-color-input');
????//?確定按鈕邏輯
????this.$Dom.pickerSure?=?getELByClass(ele,?'ew-color-sure');
????initColor(this,?config);
????//初始化面板的left偏移和top偏移
????const?panelWidth?=?this.panelWidth?=?parseInt(util.getCss(this.$Dom.pickerPanel,?'width'));
????const?panelHeight?=?this.panelHeight?=?parseInt(util.getCss(this.$Dom.pickerPanel,?'height'));
????const?rect?=?util.getRect(ele);
????this.panelLeft?=?rect.left;
????this.panelTop?=?rect.top?+?rect.height;
接著我們開(kāi)始初始化預(yù)定義顏色邏輯:
????//?預(yù)定義顏色邏輯
????if?(this.$Dom.preDefineItem.length)?{
????????initPreDefineHandler(util.ewObjToArray(this.$Dom.preDefineItem),?scope);
????}
????function?initPreDefineHandler(items,?context)?{
????????//?get?the?siblings
????????const?siblings?=?el?=>?Array.prototype.filter.call(el.parentElement.children,?child?=>?child?!==?el);
????????items.map(item?=>?{
????????????const?clickHandler?=?event?=>?{
????????????????util.addClass(item,?'ew-pre-define-color-active');
????????????????siblings(item).forEach(sibling?=>?util.removeClass(sibling,?'ew-pre-define-color-active'))
????????????????const?bgColor?=?util.getCss(event.target,?'background-color');
????????????????context.hsvaColor?=?colorRgbaToHsva(bgColor);
????????????????setColorValue(context,?context.panelWidth,?context.panelHeight,?true);
????????????????changeElementColor(context);
????????????};
????????????const?blurHandler?=?event?=>?util.removeClass(event.target,?'ew-pre-define-color-active');
????????????[{?type:?"click",?handler:?clickHandler?},?{?type:?"blur",?handler:?blurHandler?}].forEach(t?=>?{
????????????????if?(!context.config.disabled?&&?util.ewObjToArray(item.classList).indexOf('ew-pre-define-color-disabled')?===?-1)?{
????????????????????util.on(item,?t.type,?t.handler);
????????????????}
????????????});
????????})
????}
然后我們開(kāi)始初始化動(dòng)畫(huà)邏輯:
??initAnimation(scope);
??function?initAnimation(context)?{
??????//顏色選擇器打開(kāi)的動(dòng)畫(huà)初始設(shè)置
??????const?expression?=?getAnimationType(context);
??????util.setCss(context.$Dom.picker,?(expression???'display'?:?'opacity'),?(expression???'none'?:?0))
??????let?pickerWidth?=?0,?sliderWidth?=?0,?sliderHeight?=?0;
??????let?isVerticalAlpha?=?!context.isAlphaHorizontal;
??????let?isVerticalHue?=?!context.isHueHorizontal;
??????let?isHue?=?context.config.hue;
??????let?isAlpha?=?context.config.alpha;
??????if?(isAlpha?&&?isHue?&&?isVerticalAlpha?&&?isVerticalHue)?{
??????????pickerWidth?=?320;
??????????sliderWidth?=?28;
??????}?else?if?(isVerticalAlpha?&&?isAlpha?&&?(!isVerticalHue?||?!isHue)?||?(isVerticalHue?&&?isHue?&&?(!isVerticalAlpha?||?!isAlpha)))?{
??????????pickerWidth?=?300;
??????????sliderWidth?=?sliderHeight?=?14;
??????}?else?{
??????????pickerWidth?=?280;
??????????sliderHeight?=?isAlpha?&&?isHue?&&?!isVerticalHue?&&?!isVerticalAlpha???30?:?14;
??????}
??????util.setCss(context.$Dom.picker,?'min-width',?pickerWidth?+?'px');
??????if?(context.$Dom.horizontalSlider)?{
??????????util.setCss(context.$Dom.horizontalSlider,?'height',?sliderHeight?+?'px');
??????}
??????if?(context.$Dom.verticalSlider)?{
??????????util.setCss(context.$Dom.verticalSlider,?'width',?sliderWidth?+?'px');
??????}
??}
接下來(lái),就是我們的一些功能邏輯了,讓我們一一來(lái)實(shí)現(xiàn)吧,首先我們需要的實(shí)現(xiàn)的是點(diǎn)擊色塊打開(kāi)或者關(guān)閉顏色選擇器面板。如下所示:
//?色塊
????if?(!config.disabled){
??????util.on(this.$Dom.box,?'click',?()?=>?handlePicker(ele,?scope,?(flag)?=>?{
????????if?(flag?&&?scope.config.isClickOutside)?{
????????????initColor(this,?config);
????????????setColorValue(scope,?scope.panelWidth,?scope.panelHeight,?false);
????????????handleClickOutSide(scope,?scope.config);
????????}
??????}));
????}
這里的邏輯也不復(fù)雜,就是判斷是否禁用,然后為盒子元素添加點(diǎn)擊事件,在這里核心的功能就是handlePicker方法,我們可以看到傳入3個(gè)參數(shù),第一個(gè)參數(shù)為當(dāng)前根容器元素,第二個(gè)參數(shù)則是當(dāng)前執(zhí)行上下文對(duì)象,第三個(gè)參數(shù)則是一個(gè)回調(diào)函數(shù),用來(lái)做一些細(xì)節(jié)處理。setColorValue方法暫時(shí)先不作說(shuō)明,而initColor方法我們前面已經(jīng)講過(guò),handleClickOutSide方法我們將在講完handlePicker方法之后再做介紹,現(xiàn)在讓我們先來(lái)看一下handlePicker這個(gè)方法吧。
export?function?handlePicker(el,?scope,callback)?{
????scope._private.pickerFlag?=?!scope._private.pickerFlag;
????openAndClose(scope);
????initColor(scope,?scope.config);
????setColorValue(scope,?scope.panelWidth,?scope.panelHeight,?false);
????if?(util.isFunction(scope.config.togglePicker)){
????????scope.config.togglePicker(el,?scope._private.pickerFlag,scope);
????}
????if(util.isFunction(callback))callback(scope._private.pickerFlag);
}
可以看到,這個(gè)方法的核心操作是改變顏色選擇器的狀態(tài),最重要的就是openAndClose方法呢,讓我們一起來(lái)看一下吧,
export?function?openAndClose(scope)?{
????const?time?=?scope.config.pickerAnimationTime;
????scope._private.pickerFlag???open(getAnimationType(scope),?scope.$Dom.picker,time)?:?close(getAnimationType(scope),?scope.$Dom.picker,time);
}
export?function?getAnimationType(scope)?{
????return?scope.config.pickerAnimation;
}
這個(gè)方法就是獲取動(dòng)畫(huà)執(zhí)行時(shí)間,然后根據(jù)pickerFlag來(lái)判斷是開(kāi)啟還是關(guān)閉顏色選擇器,核心的就是open與close方法,兩者都接收3個(gè)參數(shù),第一個(gè)則是動(dòng)畫(huà)的類型,第二個(gè)則是顏色選擇器面板元素,第三個(gè)則是動(dòng)畫(huà)執(zhí)行時(shí)間。我們分別來(lái)看一下:
1.open方法
export?function?open(expression,?picker,time?=?200)?{
????time?=?time?>?10000???10000?:?time;
????let?animation?=?'';
????switch(expression){
????????case?'opacity':
????????????animation?=?'fadeIn';
????????????break;
????????default:
????????????animation?=?'slideDown';
????}
????return?ani[animation](picker,?time);
}
2.close方法
export?function?close(expression,?picker,time?=?200)?{
????time?=?time?>?10000???10000?:?time;
????let?animation?=?'';
????switch(expression){
????????case?'opacity':
????????????animation?=?'fadeOut';
????????????break;
????????default:
????????????animation?=?'slideUp';
????}
????return?ani[animation](picker,?time);
}
可以看到,我們?cè)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(60, 112, 198);">open與close方法內(nèi)部對(duì)時(shí)間做了一次限制處理,然后判斷動(dòng)畫(huà)類型來(lái)決定調(diào)用哪種動(dòng)畫(huà)來(lái)實(shí)現(xiàn)顏色選擇器的開(kāi)啟和關(guān)閉。到這里,我們還少實(shí)現(xiàn)了一個(gè)方法,那就是handleClickOutSide,讓我們來(lái)一起看一下這個(gè)方法的實(shí)現(xiàn):
export?function?handleClickOutSide(context,?config)?{
????util.clickOutSide(context,?config,?()?=>?{
????????if?(context._private.pickerFlag)?{
????????????context._private.pickerFlag?=?false;
????????????closePicker(getAnimationType(config.pickerAnimation),?context.$Dom.picker,config.pickerAnimationTime);
????????}
????});
}
可以看到,我們主要是對(duì)顏色選擇器面板如果處于開(kāi)啟狀態(tài)做的一個(gè)操作,也就是點(diǎn)擊不包含盒子元素區(qū)域以外的空間,我們都要關(guān)閉顏色選擇器面板。這里設(shè)計(jì)到如何去實(shí)現(xiàn)判斷我們的鼠標(biāo)點(diǎn)擊是在元素的區(qū)域之外呢?有2種方式來(lái)實(shí)現(xiàn),第一種判斷我們點(diǎn)擊的DOM元素是否是顏色選擇器元素以及其子元素節(jié)點(diǎn)即可,也就是說(shuō)我們只需要判斷我們點(diǎn)擊的元素如果是顏色選擇器面板容器元素或者是其子元素,我們都不能關(guān)閉顏色選擇器,并且當(dāng)然顏色選擇器面板還要處于開(kāi)啟中的狀態(tài)。另一種就是通過(guò)坐標(biāo)值的計(jì)算,判斷鼠標(biāo)點(diǎn)擊的坐標(biāo)區(qū)間是否在顏色選擇器面板的坐標(biāo)區(qū)域內(nèi),這里我們采用第二種實(shí)現(xiàn)方式,讓我們一起來(lái)看一下吧。
util["clickOutSide"]?=?(context,?config,?callback)?=>?{
????const?mouseHandler?=?(event)?=>?{
????????const?rect?=?util.getRect(context.$Dom.picker);
????????const?boxRect?=?util.getRect(context.$Dom.box);
????????const?target?=?event.target;
????????if?(!target)?return;
????????const?targetRect?=?util.getRect(target);
????????//?利用rect來(lái)判斷用戶點(diǎn)擊的地方是否在顏色選擇器面板區(qū)域之內(nèi)
????????if?(targetRect.x?>=?rect.x?&&?targetRect.y?>=?rect.y?&&?targetRect.width?<=?rect.width)?return;
????????//?如果點(diǎn)擊的是盒子元素
????????if?(targetRect.x?>=?boxRect.x?&&?targetRect.y?>=?boxRect.y?&&?targetRect.width?<=?boxRect.width?&&?targetRect.height?<=?boxRect.height)?return;
????????callback();
????????setTimeout(()?=>?{
????????????util.off(document,?util.eventType[0],?mouseHandler);
????????},?0);
????}
????util.on(document,?util.eventType[0],?mouseHandler);
}
可以看到,我們是通過(guò)比較x與y坐標(biāo)的大小從而確定是否點(diǎn)擊的區(qū)域?qū)儆陬伾x擇器面板區(qū)域,從而確定顏色選擇器的關(guān)閉狀態(tài)。當(dāng)然這也是我們默認(rèn)會(huì)調(diào)用的,當(dāng)然我們也提供了一個(gè)可選項(xiàng)來(lái)確定是否可以通過(guò)點(diǎn)擊元素區(qū)域之外的空間關(guān)閉顏色選擇器面板。如下:
if?(config.isClickOutside)?{
???handleClickOutSide(this,?config);
}
代碼不復(fù)雜,很容易就理解了。接下來(lái),我們來(lái)看alpha透明度的邏輯的實(shí)現(xiàn)。如下:
if?(!config.disabled)?{
????this.bindEvent(this.$Dom.alphaBarThumb,?(scope,?el,?x,?y)?=>?changeAlpha(scope,?y));
????util.on(this.$Dom.alphaBar,?'click',?event?=>?changeAlpha(scope,?event.y));
}
可以看到,我們這里首先需要判斷是否禁用,然后我們需要2種方式給透明度柱子添加事件邏輯,第一種就是拖拽透明度柱子的滑塊元素所觸發(fā)的拖拽事件,第二種則是點(diǎn)擊透明度柱子的事件,這其中涉及到了一個(gè)changeAlpha事件。我們來(lái)看一下:
export?function?changeAlpha(context,?position)?{
??let?value?=?setAlphaHuePosition(context.$Dom.alphaBar,context.$Dom.alphaBarThumb,position);
??let?currentValue?=?value.barPosition?-?value.barThumbPosition?<=?0???0?:?value.barPosition?-?value.barThumbPosition;?
??let?alpha?=?context.isAlphaHorizontal???1?-?currentValue?/?value.barPosition?:?currentValue?/?value.barPosition;
??context.hsvaColor.a?=?alpha?>=?1???1?:?alpha.toFixed(2);
??changeElementColor(context,?true);
}
這個(gè)方法又涉及到了2個(gè)方法setAlphaHuePosition與changeElementColor。我們分別來(lái)看一下:
function?setAlphaHuePosition(bar,thumb,position){
????const?positionProp?=?'y';
????const?barProp?=?'top';
????const?barPosition?=?bar.offsetHeight,
??????????barRect?=?util.getRect(bar);
????const?barThumbPosition?=?Math.max(0,Math.min(position?-?barRect[positionProp],barPosition));
????????util.setCss(thumb,barProp,barThumbPosition?+'px');
????????return?{
????????????barPosition,
????????????barThumbPosition
????????}
}
可以看到,這里我們主要的邏輯操作就是規(guī)范化樣式處理,也就是說(shuō)我們拖動(dòng)滑塊改變的是垂直方向上的top偏移(未來(lái)會(huì)考慮加入水平方向也就是left偏移),所以單獨(dú)抽取出來(lái)做一個(gè)公共的方法,這個(gè)top偏移會(huì)有一個(gè)最大值與最小值的比較。接下來(lái),我們來(lái)看changeElementColor方法的實(shí)現(xiàn):
?export?function?changeElementColor(scope,?isAlpha)?{
????const?color?=?colorHsvaToRgba(scope.hsvaColor);
????let?newColor?=?isAlpha?||?scope.config.alpha???color?:?colorRgbaToHex(color);
????scope.$Dom.pickerInput.value?=?newColor;
????scope.prevInputValue?=?newColor;
????changeAlphaBar(scope);
????if?(util.isFunction(scope.config.changeColor))scope.config.changeColor(newColor);
}
顯然這個(gè)方法的核心目的就是處理顏色值的改變,我們有2個(gè)參數(shù),第一個(gè)參數(shù)則是當(dāng)前上下文,第二個(gè)參數(shù)用于判斷透明度柱是否開(kāi)啟。先利用colorHsvaToRgba方法將當(dāng)前的顏色值轉(zhuǎn)換成rgba顏色,然后判斷如果開(kāi)啟了透明度柱,則不需要進(jìn)行轉(zhuǎn)換,否則就需要轉(zhuǎn)換成hex顏色模式,然后我們把新的顏色值傳給input元素。并且緩存了一下這個(gè)顏色值,然后這里需要注意一下,如果改變了顏色值,則有可能透明度會(huì)改變,因此,需要再次調(diào)用changeAlphaBar方法來(lái)改變透明度柱的功能。最后我們暴露了一個(gè)changeColor方法接口給用戶使用。
前面還提到了一個(gè)bindEvent方法,我們接下來(lái)來(lái)看一下這個(gè)bindEvent方法的實(shí)現(xiàn)。如下:
export?function?bindEvent(el,?callback,?bool)?{
????const?context?=?this;
????const?callResult?=?event?=>?{
????????context.moveX?=?util.eventType[0].indexOf('touch')?>?-1???event.changedTouches[0].clientX?:?event.clientX;
????????context.moveY?=?util.eventType[0].indexOf('touch')?>?-1???event.changedTouches[0].clientY?:?event.clientY;
????????bool???callback(context,?context.moveX,?context.moveY)?:?callback(context,?el,?context.moveX,?context.moveY);
????}
????const?handler?=?()?=>?{
????????const?moveFn?=?e?=>?{?e.preventDefault();?callResult(e);?}
????????const?upFn?=?()?=>?{
????????????util.off(document,?util.eventType[1],?moveFn);
????????????util.off(document,?util.eventType[2],?upFn);
????????}
????????util.on(document,?util.eventType[1],?moveFn);
????????util.on(document,?util.eventType[2],?upFn);
????}
????util.on(el,?util.eventType[0],?handler);
}
這個(gè)方法的核心就是在PC端監(jiān)聽(tīng)onmousedown,onmousemove,onmouseup事件,在移動(dòng)端監(jiān)聽(tīng)touchstart,touchmove,touchend事件并將當(dāng)前上下文,x坐標(biāo)以及y坐標(biāo)回調(diào)出去。
接下來(lái),讓我們繼續(xù)。我們來(lái)實(shí)現(xiàn)hue色調(diào)柱的邏輯,它的邏輯和透明度柱很相似。
if?(!config.disabled)?{
????//hue的點(diǎn)擊事件
????util.on(this.$Dom.hueBar,?'click',?event?=>?changeHue(scope,?event.y))
????//hue?軌道的拖拽事件
????this.bindEvent(this.$Dom.hueBarThumb,?(scope,?el,?x,?y)?=>?changeHue(scope,?y));
}
可以看到,我們同樣是判斷是否禁用,然后給色調(diào)柱添加點(diǎn)擊事件以及給hue滑塊添加拖拽事件。這里也就核心實(shí)現(xiàn)了一個(gè)changeHue方法。讓我們來(lái)看一下吧。
export?function?changeHue(context,?position)?{
????const?{?$Dom:{?hueBar,hueThumb,pickerPanel?},_private:{hsvaColor}}?=?context;
????let?value?=?setAlphaHuePosition(hueBar,?hueThumb,?position);
????const?{?barThumbPosition,barPosition?}?=?value;
????context.hsvaColor.h?=?cloneColor(hsvaColor).h?=?parseInt(360?*?barThumbPosition?/?barPosition);
????util.setCss(pickerPanel,?'background',?colorRgbaToHex(colorHsvaToRgba(cloneColor(context.hsvaColor))));
????changeElementColor(context);
}
這個(gè)方法,我們首先同樣是獲取到一個(gè)值,由前面的顏色算法我們應(yīng)該知道,色彩的角度限制在0~360之間,然后我們通過(guò)360 * barThumbPosition / barPosition得到了色彩也就是h的相關(guān)值。然后我們需要修改顏色面板的背景樣式。然后調(diào)用changeElementColor方法(這個(gè)在前面已經(jīng)講過(guò))。前面我們遺留了一個(gè)方法,叫做changeAlphaBar,讓我們來(lái)看一下這個(gè)方法做了什么。
export?function?changeAlphaBar(scope)?{
????if?(!scope.$Dom.alphaBarBg)?return;
????let?position?=?'to?top';
????util.setCss(scope.$Dom.alphaBarBg,?'background',?'linear-gradient('+?position?+','?+?colorHsvaToRgba(scope.hsvaColor,0)?+?'?0%,'?+?colorHsvaToRgba(scope.hsvaColor,1)?+?'?100%)');
}
可以看到,實(shí)際上我們就是對(duì)透明度柱的背景色做了一個(gè)修改。由于我們的透明度柱子不一定存在(因?yàn)橛捎脩糇远x是否顯示),所以這里我們是需要做一個(gè)判斷的。
接下來(lái),讓我們繼續(xù)來(lái)實(shí)現(xiàn)一下顏色面板組件的相關(guān)邏輯功能。其實(shí)它的邏輯與透明度柱和色彩柱一樣,都是分為拖拽和點(diǎn)擊。如下所示:
//顏色面板點(diǎn)擊事件
util.on(this.$Dom.pickerPanel,?'click',?event?=>?onClickPanel(scope,?event));
//顏色面板拖拽元素拖拽事件
this.bindEvent(this.$Dom.pickerCursor,?(scope,?el,?x,?y)?=>?{
????const?left?=?Math.max(0,?Math.min(x?-?scope._private.panelLeft,?panelWidth));
????const?top?=?Math.max(0,?Math.min(y?-?scope._private.panelTop,?panelHeight));
????changeCursorColor(scope,?left?+?4,?top?+?4,?panelWidth,?panelHeight);
});
我們先來(lái)看點(diǎn)擊邏輯,同樣的是監(jiān)聽(tīng)面板的點(diǎn)擊事件,然后調(diào)用onClickPanel方法,我們來(lái)看一下這個(gè)方法的實(shí)現(xiàn)。
export?function?onClickPanel(scope,?eve)?{
????if?(eve.target?!==?scope.$Dom.pickerCursor)?{
????????//臨界值處理
????????const?moveX?=?eve.layerX;
????????const?moveY?=?eve.layerY;
????????const?{?_private:{?panelWidth,panelHeight?}}?=?context;
????????const?left?=?moveX?>=?panelWidth?-?1???panelWidth?:?moveX?<=?0???0?:?moveX;
????????const?top?=?moveY?>=?panelHeight?-?2???panelHeight?:?moveY?<=?0???0?:?moveY;
????????changeCursorColor(scope,?left?+?4,?top?+?4,panelWidth,panelHeight)
????}
}
可以看到,我們所做的操作就是獲取一個(gè)x坐標(biāo)和y坐標(biāo),然后去設(shè)置拖拽游標(biāo)的left和top偏移,這里會(huì)有臨界值的處理。稍微寬度減1和高度減2是做一層偏差處理。然后再次調(diào)用changeCursorColor方法,我們繼續(xù)來(lái)看這個(gè)方法的實(shí)現(xiàn)。
export?function?changeCursorColor(scope,?left,?top,?panelWidth,?panelHeight)?{
????util.setSomeCss(scope.$Dom.pickerCursor,?[{?prop:?'left',?value:?left?+?'px'?},?{?prop:?'top',?value:?top?+?'px'?}])
????const?s?=?parseInt(100?*?(left?-?4)?/?panelWidth);
????const?v?=?parseInt(100?*?(panelHeight?-?(top?-?4))?/?panelHeight);
????//需要減去本身的寬高來(lái)做判斷
????scope.hsvaColor.s?=?s?>?100???100?:?s?0???0?:?s;
????scope.hsvaColor.v?=?v?>?100???100?:?v?0???0?:?v;
????changeElementColor(scope);
}
可以看到這個(gè)方法我們所做的操作就是設(shè)置游標(biāo)元素的偏移量,以及它的偏移量所代表的的就是hsva顏色模式中的s和v,然后我們?cè)俅握{(diào)用changeElementColor方法就可以改變顏色值了。
讓我們繼續(xù)看清空按鈕的事件邏輯,如下所示:
util.on(this.$Dom.pickerClear,?'click',?()?=>?onClearColor(scope));
也就是添加點(diǎn)擊事件的監(jiān)聽(tīng),然后再事件的回調(diào)函數(shù)中調(diào)用onClearColor方法,接下來(lái),我們看onClearColor方法。如下所示:
export?function?onClearColor(scope)?{
????scope._private.pickerFlag?=?false;
????closePicker(getAnimationType(scope.config.pickerAnimation),scope.$Dom.picker,scope.config.pickerAnimationTime);
????scope.config.defaultColor?=?scope._private.color?=?"";
????scope.config.clear(scope.config.defaultColor,?scope);
}
可以看到我們所做的操作比較簡(jiǎn)單,就是重置顏色選擇器開(kāi)啟狀態(tài),然后調(diào)用關(guān)閉顏色選擇器方法關(guān)閉顏色選擇器,然后重置我們的顏色,再回調(diào)一個(gè)clear方法接口給用戶使用。同樣的道理,我們的確定按鈕的邏輯也就是如此了。如下所示:
util.on(this.$Dom.pickerSure,?'click',?()?=>?onSureColor(scope));
也就是添加點(diǎn)擊事件的監(jiān)聽(tīng),然后再事件的回調(diào)函數(shù)中調(diào)用onSureColor方法,接下來(lái),我們看onSureColor方法。如下所示:
export?function?onSureColor(scope)?{
????const?result?=?scope.config.alpha???colorHsvaToRgba(scope._private.hsvaColor)?:?colorRgbaToHex(colorHsvaToRgba(scope._private.hsvaColor));
????scope._private.pickerFlag?=?false;
????closePicker(getAnimationType(scope.config.pickerAnimation),scope.$Dom.picker,scope.config.pickerAnimationTime);
????scope.config.defaultColor?=?scope._private.color?=?result;
????changeElementColor(scope);
????scope.config.sure(result,?scope);
}
可以看到這個(gè)操作的邏輯也比較簡(jiǎn)單,類似于清空按鈕的邏輯,我們不外乎需要設(shè)置顏色值,然后回調(diào)一個(gè)sure方法給用戶,這個(gè)方法回調(diào)兩個(gè)參數(shù),第一個(gè)參數(shù)為當(dāng)前選中的顏色值,第二個(gè)參數(shù)則是當(dāng)前上下文對(duì)象。另外,我們還需要調(diào)用changeElementColor方法來(lái)改變顏色值。
接下來(lái),讓我們繼續(xù)來(lái)實(shí)現(xiàn)一下input框的相關(guān)邏輯功能,這也是我們的最后一個(gè)邏輯。首先我們需要確定的就是,當(dāng)input框移開(kāi)焦點(diǎn)的時(shí)候,就意味著更改顏色值。所以我們監(jiān)聽(tīng)它的移開(kāi)焦點(diǎn)事件,然后額外封裝了一個(gè)方法。當(dāng)然在這之前,我們先需要監(jiān)聽(tīng)禁用邏輯,如下所示:
//?禁用邏輯
if?(config.disabled)?{
????if?(!util.hasClass(this.$Dom.pickerInput,?'ew-input-disabled'))?{
????????util.addClass(this.$Dom.pickerInput,'ew-input-disabled');
????}
????if?(!util.hasClass(this.$Dom.picker,?'ew-color-picker-disabled'))?{
????????util.addClass(this.$Dom.picker,'ew-color-picker-disabled');
????}
????this.$Dom.pickerInput.disabled?=?true;
????return?false;
}
可以看到,以上的邏輯,我們就是判斷用戶是否傳入了disabled屬性,然后判斷input元素是否還有我們自定義的禁用類名ew-input-disabled,如果沒(méi)有則添加該類名,同樣的,我們?yōu)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(60, 112, 198);">picker也做相同的邏輯,最后我們將input元素的disabled屬性設(shè)置為true。接下來(lái)我們來(lái)看blur事件的實(shí)現(xiàn):
util.on(this.$Dom.pickerInput,?'blur',?event?=>?onInputColor(scope,?event.target.value));
這段代碼很簡(jiǎn)單,就是添加監(jiān)聽(tīng)事件,接下來(lái),我們來(lái)看onInputColor方法的實(shí)現(xiàn)。如下:
?export?function?onInputColor(scope,?value)?{
????if?(!isValidColor(value))?return;
????//?兩者相等,說(shuō)明用戶沒(méi)有更改顏色?
????if?(util.removeAllSpace(scope.prevInputValue)?===?util.removeAllSpace(value))return;
????let?color?=?scope.config.alpha???colorRgbaToHsva(value)?:?colorRgbaToHsva(colorHexToRgba(value));
????scope.hsvaColor?=?color;
????setColorValue(scope,?scope.panelWidth,?scope.panelHeight,true);
}
這段代碼的邏輯也不復(fù)雜,首先判斷輸入框的值是否是合格的顏色值或者判斷當(dāng)前值和我們緩存的值是否相同,如果不是合格的顏色值或者與緩存的值相同則不作任何操作。然后我們?cè)俑鶕?jù)是否開(kāi)啟了透明度柱來(lái)判斷是否需要調(diào)用colorHexToRgba方法來(lái)將顏色值轉(zhuǎn)換成rgba顏色,然后再使用colorRgbaToHsva方法來(lái)將顏色值轉(zhuǎn)換成hsva的顏色。然后再賦值。最后再調(diào)用setColorValue方法來(lái)賦值。接下來(lái),我們就來(lái)看setColorValue方法的實(shí)現(xiàn)。如下:
export?function?setColorValue(context,?panelWidth,?panelHeight,boxChange)?{
????changeElementColor(context);
????context._private.prevInputValue?=?context.$Dom.pickerInput.value;
????let?sliderBarHeight?=?0;
????let?l?=?parseInt(context.hsvaColor.s?*?panelWidth?/?100),
????????t?=?parseInt(panelHeight?-?context.hsvaColor.v?*?panelHeight?/?100);
????[
????????{
????????????el:?context.$Dom.pickerCursor,
????????????prop:?'left',
????????????value:?l?+?4?+?'px'
????????},
????????{
????????????el:?context.$Dom.pickerCursor,
????????????prop:?'top',
????????????value:?t?+?4?+?'px'
????????},
????????{
????????????el:?context.$Dom.pickerPanel,
????????????prop:?'background',
????????????value:?colorRgbaToHex(colorHsvaToRgba(cloneColor(context.hsvaColor)))
????????}
????].forEach(item?=>?util.setCss(item.el,?item.prop,?item.value));
????getSliderBarPosition(context.$Dom.hueBar,(position,prop)?=>?{
????????util.setCss(context.$Dom.hueThumb,?prop,?parseInt(context.hsvaColor.h?*?position?/?360)?+?'px');
????});
????if?(context.config.alpha)?{
????????getSliderBarPosition(context.$Dom.alphaBar,(position,prop)?=>?{
????????????util.setCss(context.$Dom.alphaBarThumb,?prop,?position?-?context.hsvaColor.a?*?position?+?'px');
????????});
????}
}
export?function?getSliderBarPosition(bar,callback){
????let?sliderPosition?=?bar.offsetHeight;
????let?sliderProp?=?'top';
????callback(sliderPosition,sliderProp);
}
這個(gè)方法的實(shí)現(xiàn)稍微有點(diǎn)復(fù)雜,實(shí)際上這個(gè)方法在前面我們已經(jīng)用到過(guò),只是沒(méi)有講解。接下來(lái),讓我們來(lái)一一分析這個(gè)方法到底做了什么。首先,調(diào)用了changeElementColor方法賦值,其次緩存當(dāng)前的輸入框的顏色值,然后計(jì)算顏色面板游標(biāo)元素的left和top偏移量,然后分別設(shè)置它們,再然后設(shè)置顏色面板的背景色。以及設(shè)置色彩柱的偏移量。如果透明度柱子存在,則也要設(shè)置透明度柱子的偏移量。
到目前為止,我們所要實(shí)現(xiàn)的顏色選擇器的基本功能就已經(jīng)完成,接下來(lái),我們來(lái)對(duì)我們的文檔做一個(gè)總結(jié)。我們從分析每一個(gè)顏色選擇器的模塊開(kāi)始,對(duì)應(yīng)的結(jié)構(gòu)及樣式我們都是一一分析了,然后再細(xì)化到每一個(gè)功能。每一個(gè)顏色選擇器的模塊如下:
顏色色塊 顏色面板 色調(diào)柱 透明度柱 輸入框 清空與確定按鈕 預(yù)定義顏色元素列表
再然后,我們對(duì)照每一個(gè)模塊去一一實(shí)現(xiàn)它們的功能。在這些功能中,我們學(xué)到了哪些東西呢?
閉包。(也就是說(shuō)我們?cè)谀骋粋€(gè)作用域中訪問(wèn)其它作用域中的變量。例如:bindEvent方法的實(shí)現(xiàn)) 定時(shí)器。(如動(dòng)畫(huà)函數(shù)的實(shí)現(xiàn)) 顏色轉(zhuǎn)換算法。 正則表達(dá)式。 面向?qū)ο蟮木幊獭?/section> 如何實(shí)現(xiàn)點(diǎn)擊目標(biāo)區(qū)域之外的邏輯功能
當(dāng)然還有很多,細(xì)細(xì)品味下來(lái),我們應(yīng)該知道遠(yuǎn)遠(yuǎn)不止如此,但是我們的文檔確實(shí)到此為止了,后續(xù)應(yīng)該還會(huì)有擴(kuò)展。讓我們后面再見(jiàn),感謝大家的觀看,祝大家能夠?qū)W習(xí)愉快。
如果覺(jué)得本文不夠詳細(xì),可以查看視頻課程,感謝支持。當(dāng)然你也可以查看源碼。
