<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從零開(kāi)始實(shí)現(xiàn)一個(gè)顏色選擇器(原生JavaScript實(shí)現(xiàn))

          共 38690字,需瀏覽 78分鐘

           ·

          2021-10-22 16:24

          作者:夕水

          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">&times;

          ????

          ??


          這里我們肯定是通過(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è)記錄:

          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:centeralign-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é)如下:

          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-radius50%即可以將一個(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">
          ????type="button"?class="ew-color-drop-btn?ew-color-clear">清空
          ????type="button"?class="ew-color-drop-btn?ew-color-sure">確定
          ??

          然后樣式也沒(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???????r?=?t;
          ??????g?=?p?+?q;
          ??????b?=?p;
          ????}?else?if?(h???????r?=?t?-?q;
          ??????g?=?t;
          ??????b?=?p;
          ????}?else?if?(h???????r?=?p;
          ??????g?=?t;
          ??????b?=?p?+?q;
          ????}?else?if?(h???????r?=?p;
          ??????g?=?t?-?q;
          ??????b?=?t;
          ????}?else?if?(h???????r?=?p?+?q;
          ??????g?=?p;
          ??????b?=?t;
          ????}?else?if?(h???????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???????if?(t?return?p?+?(q?-?p)?*?6?*?t;
          ??????if?(t?return?q;
          ??????if?(t?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???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???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é)如下:

          接下來(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};">&times;`;
          ????//透明度
          ????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?=?`"ew-color-clear?ew-color-drop-btn">${?config.clearText?}`;
          ????}
          ????if?(config.hasSure)?{
          ????????sureHTML?=?`"ew-color-sure?ew-color-drop-btn">${?config.sureText?}`;
          ????}
          ????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?=?`"ew-color-mode-title">${this.colorMode[1]}`;
          ????}
          ????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)分析一下,我們可以大致得到主要的邏輯有:

          我們接下來(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)閉顏色選擇器,核心的就是openclose方法,兩者都接收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è)方法setAlphaHuePositionchangeElementColor。我們分別來(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?????scope.hsvaColor.v?=?v?>?100???100?:?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è)顏色選擇器的模塊如下:

          再然后,我們對(duì)照每一個(gè)模塊去一一實(shí)現(xiàn)它們的功能。在這些功能中,我們學(xué)到了哪些東西呢?

          1. 閉包。(也就是說(shuō)我們?cè)谀骋粋€(gè)作用域中訪問(wèn)其它作用域中的變量。例如:bindEvent方法的實(shí)現(xiàn))
          2. 定時(shí)器。(如動(dòng)畫(huà)函數(shù)的實(shí)現(xiàn))
          3. 顏色轉(zhuǎn)換算法。
          4. 正則表達(dá)式。
          5. 面向?qū)ο蟮木幊獭?/section>
          6. 如何實(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)然你也可以查看源碼。

          瀏覽 47
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男男久久久 | 日韩AI视频在线免费观看 | 天天干天天舔天天操 | 亚州视频一区 | 蜜桃熟女网 |