本教程的目的是为了帮助初学者更好的掌握DOM和数组相关操作,实例效果如下图所示。
可以看到实例中的拾色器分为三个部分:拾色区域、色系区域、显示颜色区域。先写出这三个部分的html代码,如下所示:
<div class="color_container">
<div class="main_wrap"> <!--拾色区域-->
<div class="main_drag" id="mainDrag"></div>
<div class="main_con" id="mainCon"></div>
</div>
<div class="side_wrap"> <!--色系区域-->
<div class="side_drag" id="sideDrag"></div>
<div class="side_con" id="sideCon"></div>
</div>
<div class="show_color" id="findColor"><!--显示区域-->
<div class="color_full" id="colorFull"></div>
<div class="color_text" id="colorText">
R:<input type="text">
G:<input type="text">
B:<input type="text">
</div>
</div>
</div>
读者可以自己编写样式,也可以从 《原生js制作简易DOM拾色器实例教程》源码 下载本教程关联实例中复制。
开始编写javascript代码,声明一个fnColorPicker函数,把拾色器功能封装起来。
function fnColorPicker(){
}
接下来完成函数内具体代码。分析一下功能,设定三个区域的开发顺序:色系区域 > 拾色区域 > 显示颜色区域。
- 生成色系区域的DOM
色系区域实际上是不同色系色块组成(DOM元素),所以现在要做的工作就是使用js生成色块。生成色块有两个条件,一是有多少个色块,二是每个色块的rgb值分别是多少。知道这两个条件就可以很轻松拼成区域内的DOM元素。
每个色块的高度为1,宽度和色系区域一致,需要根据色系区域的高度计算出需要多少个色块,也就是共有多少种颜色。
1.1 获取色系区域及高度,如下所示:
//获取色系区域
var eSideCon = document.getElementById('sideCon');
//获取色系区域的高度
var nSideH = eSideCon.offsetHeight;
色系区域的高度nSideH实际上就是色块的数量,给每一个色块添加正确的背景颜色就可达到效果。分析一下色系区域的颜色变化是:红>黄>绿>青>蓝>紫>红,共七种颜色从上至下的渐变就形成了色系区域。在下表中列出了颜色每次变化的rgb值及修改规则:
颜色渐变 | rgb颜色数组变化 | 修改的rgb值 | 在数组中的下标 | 修改方向 |
---|---|---|---|---|
红>黄 | [255,0,0] → [255,255,0] | g | 1 | ↑ |
黄>绿 | [255,255,0] → [0,255,0] | r | 0 | ↓ |
绿>青 | [0,255,0] → [0,255,255] | b | 2 | ↑ |
青>蓝 | [0,255,255] → [0,0,255] | g | 1 | ↓ |
蓝>紫 | [0,0,255] → [255,0,255] | r | 0 | ↑ |
紫>红 | [255,0,255] → [255,0,0] | b | 2 | ↓ |
知道了颜色变化规则,再来写代码就轻松很多。
1.2 声明相关变量。
根据上面表格列出的规则,声明一个rgb数组,代表色块颜色;
声明变量表示要修改的rgb值在数组中的下标;
声明变量表示要修改rgb数值是变大还是变小;
存储色系颜色的数组,用于选择色系;
存储拾色区域色块颜色的数组,用于拾取颜色。如下所示:
//rgb颜色数组,默认是第一种颜色(红色)
var aRGB = [255,0,0];
//要修改的rgb值在数组中的下标,0表示r,1表示g,2表示b,默认先修改g的值
var nRGBSub = 1;
//颜色值修改的方向,默认修改g值,从0>255,设定为true;
var bUp = true;
//存储所有色系的数组
var aSideColorStore = [];
//存储拾色区色块颜色的数组
var aMainColorStore = [];
1.3 计算颜色值每次变化的数值
这是一个很重要的数值,比如从0变成255,每次应该增加多少呢?颜色总共有6次渐变,每次渐变改变的值最多是255。所以总共颜色变化是255*6次。再除以高度,就得出每次变化的值,计算公式如下所示:
//计算颜色变化值
var nStep = Math.ceil(255*6/nMainH);
1.4 通过循环,往eSideCon色系区域中填充色块
之前已经计算出色块的数量nSideH,所以循环nSideH次,把每一个色块组合成一个字符串,再填入到eSideCon中。具体代码如下:
//声明html的字符串
var sSideHTML = '';
//循环nSideH次
for(let i=0;i<nSideH;i++){
//判断颜色值改变方向是变大还是变小
if(bUp){
//根据上表列出规则,第一次修改g值,数组的下标是1,每次增加nStep的数量
aRGB[nRGBSub] += nStep;
//颜色值最大是255,所以当增加超过255时
if(aRGB[nRGBSub]>=255){
//需要修正颜色值
aRGB[nRGBSub] = 255;
//修改下标
nRGBSub--;
if(nRGBSub<0)nRGBSub = 2;
//修改颜色值改变方向
bUp = false;
}
}else{
//每次减少nStep的数量
aRGB[nRGBSub] -= nStep;
//颜色最值小是0,所以当减少为0或以下时
if(aRGB[nRGBSub]<=0){
//修正颜色值为0
aRGB[nRGBSub] = 0;
//修改下标
nRGBSub--;
if(nRGBSub<0)nRGBSub = 2;
//修改颜色值改变方向
bUp = true;
}
}
//把色块颜色存到色系数组中
aSideColorStore.push(JSON.stringify(aRGB));
//把每一个色块添加到字符串中
sSideHTML = sSideHTML + `<div style="background:rgb(${aRGB.join(',')})"></div>`;
}
//填充到色系区域
eSideCon.innerHTML = sSideHTML;
此时效果如图所示:
- 生成拾色区域的DOM
有了色系之后,可以根据当前色系生成左边的拾色区域。拾色区域也是由很多个色块组成,需要根据拾色区域的高度和宽度来获取色块的数量。
2.1 获取拾色区域的宽度和高度
//获取拾色区域
var eMainCon = document.getElementById('mainCon');
//获取拾色区域的宽度和高度
var nMainW = eMainCon.offsetWidth;
var nMainH = eMainCon.offsetHeight;
每一个色块的大小 1 * 1,那拾色区域的色块就有nMainH行,每行nMainW个,总共数量为nMainW*nMainH。分析一下拾色区域的颜色变化,其实就是从左至右,从上至下颜色的渐变。下表列出默认拾色区域颜色变化规则:
左侧颜色 | 渐变方向 | 右侧颜色 |
---|---|---|
白色[255,255,255] | > | 红色[255,0,0] |
…… | > | …… |
↓ | ↓ | |
黑色[0,0,0] | > | 黑色[0,0,0] |
上表列出了拾色区域四个角的颜色,可以得出规则是从左至右先创建第一行色块,白色到红色的渐变;再从上至下颜色逐步加深渐变为黑色。
2.2 声明相关变量
因为每一次选择色系都需要修改拾色区的颜色。所以把生成拾色区域色块功能封装到函数中,传入color参数表示当前色系颜色。
//生成拾色区域
function fnColorSet(color){
}
在fnColorSet函数中再编写代码。拾色区其实是三种颜色的渐变,
左上角是白色,往下逐步加深;
右上角是选择色系的颜色,往下逐步加深,通过color参数传入;
下方是固定的黑色。
还需要再声明一个可变的色块颜色、html字符串和存储色块颜色的数组(用于拾取颜色),如下所示:
function fnColorSet(color){
//左侧可变颜色,默认为白色
var aLeftColor = [255,255,255];
//右侧可变颜色,因为color参数是字符串,所以要转换为数组
var aRightColor = JSON.parse(color);
//底部颜色固定黑色
var aBottomColor = [0,0,0] ;
//因为色块可变颜色从左上角开始,所以默认设置为白色
var aMainColor = [255,255,255];
//拾色区域html字符串
var sMainHTML = '';
}
2.3 通过循环,往eMainCon拾色区域中填充色块
因为区域中的色块总共是 nMainH*nMainW 个,就像一个表格。按钮表格组成原则,先循环 nMainH 次,生成每一行。再在里面嵌套循环,循环 nMainW 次,即可在每行中生成正确的列数,如下所示:
//生成拾色区域
function fnColorSet(color){
/* ... */
//重置拾色区颜色
aMainColorStore = [];
//循环每一行色块
for(let i=0;i<nMainH;i++){
//因为每一列的颜色都是往下加深渐变,所以除第一行之外每行循环都需要修改左侧和右侧颜色
if(i>0){
for(let n = 0;n<aMainColor.length;n++){
aLeftColor[n] -= Math.ceil((aLeftColor[n]-aBottomColor[n])/(nMainH-i));
aRightColor[n] -= Math.ceil((aRightColor[n]-aBottomColor[n])/(nMainH-i));
}
}
//每一行的色块颜色单独存到一个新的数组中
aMainColorStore.push([]);
//每一次循环色块都要重置为左侧颜色
aMainColor = JSON.parse(JSON.stringify(aLeftColor));
//在每一行中循环每一列色块
for(let j=0;j<nMainW;j++){
if(j!=0){ //第一个色块颜色不需要修改
//从左至右渐变颜色,因为有rgb三种颜色,所以需要再加一个循环
for(let n = 0;n<aMainColor.length;n++){
//下面的公式用于rgb值颜色渐变
aMainColor[n] -= Math.ceil((aMainColor[n]-aRightColor[n])/(nMainW-j));
}
}
//存储色块颜色
aMainColorStore[i].push(JSON.stringify(aMainColor));
//每一个色块添加到字符串中
sMainHTML = sMainHTML + `<div style="background:rgb(${aMainColor.join(',')})"></div>`;
}
}
//填充拾色区域
eMainCon.innerHTML = sMainHTML;
}
//调用函数,填充拾色区域,初始化传入红色
fnColorSet('[255,0,0]');
此时效果如图所示:
- 填充显示颜色区域
因为每次拾取颜色,显示颜色区域都要修改,所以封装到fnColorFull函数中。如下所示:
//获取显示颜色块
var eColorFull = document.getElementById('colorFull');
var eColorText = document.getElementById('colorText');
var aColorInput = eColorText.getElementsByTagName('input');
function fnColorFull(color){
//颜色参数是字符串,需要转换为数组
var color = JSON.parse(color);
// 修改显示颜色
eColorFull.style.background = 'rgb('+color.join(',')+')';
//修改RGB颜色值
for(let i=0;i<aColorInput.length;i++){
aColorInput[i].value = color[i];
}
}
//默认显示白色
fnColorFull('[255,255,255]')
- 拾色区域拾取颜色
在拾色区域有一个圆形吸管,可以通过拖动吸管来拾取颜色。在吸管上绑定事件,如下所示:
//获取吸管元素
var eMainDrag = document.getElementById('mainDrag');
//aMainColorStore数组中颜色行下标
var nSX = 0;
//aMainColorStore数组中颜色列下标
var nSY = 0;
eMainDrag.addEventListener('mousedown',function(event){
//初始化鼠标开始拖拽的点击位置
var nInitX = event.clientX;
var nInitY = event.clientY;
//初始化吸管位置
var nInitTop = this.offsetTop;
var nInitLeft = this.offsetLeft;
//选中吸管后,在document上绑定鼠标移动事件
document.onmousemove = event=>{
//鼠标移动时取消默认行为,避免选中其他元素或文字
event.preventDefault();
//获取鼠标位置
let nX = event.clientX - nInitX + nInitLeft;
let nY = event.clientY - nInitY + nInitTop;
//以下的条件用于限制吸管不能移出拾色区域
if(nY>=122){
nY = 122;
}
if(nY<=0){
nY = 0;
}
if(nX<=0){
nX = 0;
}
if(nX>=192){
nX = 192;
}
//因为用的是箭头函数,所以this还是指向吸管,修改吸管位置
this.style.top = nY + 'px';
this.style.left = nX + 'px';
//颜色赋值,因为没办法选到最后一个颜色,所以加这个公式,这样中间有些颜色选不到
nSX = nX+Math.floor(nX/26); //Math.floor(192/26) = 7;(192+7)=199,是宽度最后一个位置
nSY = nY+Math.floor(nY/16);
//填充显示颜色区域
fnColorFull(aMainColorStore[nSY][nSX]);
}
//松开鼠标后释放document上的事件
document.onmouseup = event=>{
document.onmouseup = null;
document.onmousemove = null;
}
});
- 色系区域选择颜色
通过色系上的滑块,可以修改拾色区域的颜色,代码如下所示:
//获取色系吸管
var eSideDrag = document.getElementById('sideDrag');
//在色系吸管上绑定鼠标按下事件
eSideDrag.addEventListener('mousedown',function(event){
//初始化鼠标开始拖拽的点击位置
var nInitY = event.clientY;
//初始化色系吸管位置
var nInitTop = this.offsetTop;
//色系吸管位置
var nY = null;
document.onmousemove = event=>{
//鼠标移动时取消默认行为,避免选中其他元素或文字
event.preventDefault();
//根据鼠标设置色系吸管位置
nY = event.clientY - nInitY + nInitTop;
//下面的条件限制色系吸管不能超出范围
if(nY>=122){
nY = 122;
}
if(nY<=-4){
nY = 0;
}
//因为用的是箭头函数,所以this还是指向滑块,修改滑块位置
this.style.top = nY + 'px';
}
//鼠标释放事件
document.onmouseup = event=>{
document.onmouseup = null;
document.onmousemove = null;
if(nY!==null){
//修改拾色区颜色
let n = nY+Math.floor(nY/16); //Math.floor(122/16) = 7;(122+7)=129,是高度最后一个位置
fnColorSet(aSideColorStore[n]);
//修改显示颜色区域颜色
fnColorFull(aMainColorStore[nSY][nSX]);
}
}
});
最后调用fnColorPicker函数就可以看到最终效果。本实例主要用于学习,不适合实际开发工作中使用。不但性能不太友好,而且bug也不少。想要流畅且友好的效果,可以查看我另外一篇文章 《js配合css3开发流畅的web拾色器功能》。