实现一个颜色选择器
最近经历了一波辞职,找工作,搬家这样一个过程,所以没有空写博客,现在稳定了下来,写一下过年时写过的一些东西;
这次要写的是一个颜色选择器,也许很多人都认为是不需要的,因为有h5的 api 提供类似的功能,但是作为一个探索者,怎么能不直接实现一个呢
-
首先是样式的编写
关于样式方面我仿照的是 elementUI 的结构:<div > <div id="chooseColor" ></div> <div id="pickBox" style="display: none"> <div class="colorBox"> <div class="color" style="background-color: rgb(255, 0, 0);"> <div class="white"></div> <div class="black"></div> <div class="point"> <div class="p"></div> </div> </div> <div class="colorSelect"> <div class="colorBar"></div> <div class="thumb bar"></div> </div> </div> <div class="transparency" style="background-color: rgb(255, 255, 255);"> <div class="transparencyBar"></div> <div class="thumb trans"></div> </div> <div class="operate"> <input autocomplete="off" class="rgbaText" type="text" value="rgba(255,255,255,1)"> <button id="confirm">确认</button> </div> </div> </div>
-
css 的添加
说到这里得说一个 css 的渐变色的使用:
linear-gradient
该 API 可以让颜色变成渐变色,相当好用,现在使用率很高
如这般使用:background: linear-gradient(to left, #3f87a6, #ebf8e1, #f69d3c);
第一个参数是颜色旋转角度,后面的参数都是颜色,也可以加上比例
完成后的样式,如下图:
-
初始化
chooseColor.addEventListener('click',()=>{ pick.style.display = 'block'; init(); },false); document.getElementById('confirm').addEventListener('click',()=>{ pick.style.display = 'none'; });
这2个事件是启动与确认关闭的事件
再就是说到 'init()' 这个函数,他是用来初始化查询器, div 的长宽和距幕距离,至于为什么要这样做呢,具体可以了解一下
display:none
和选择器的关系;const init = (): void => { pick = <HTMLElement>document.getElementById('pickBox'); colorElement = <HTMLElement>pick.querySelector('.color'); colorPoint = <HTMLElement>pick.querySelector('.point'); colorBar = <HTMLElement>pick.querySelector('.colorBar'); rgbaText = <HTMLInputElement>pick.querySelector('.rgbaText'); colorBarThumb = <HTMLElement>pick.querySelector('.bar.thumb'); transparency = <HTMLElement>pick.querySelector('.transparency'); transparencyBar = <HTMLElement>pick.querySelector('.transparencyBar'); transparencyThumb = <HTMLElement>pick.querySelector('.transparency .thumb'); //color长宽 colorWidth = colorElement.clientWidth; colorHeight = colorElement.clientHeight; transparencyBarWidth = transparencyBar.clientWidth; pickBoxOffsetTop = pick.getBoundingClientRect().top; pickBoxOffsetLeft = pick.getBoundingClientRect().left; };
一些工具函数:
const pxToNumber = (px: string = '0px'): number => { return Number(px.slice(0, -2)); }; const objToRGBA = (obj: rgb): string => { return `rgba(${obj.r},${obj.g},${obj.b},${transparencyCache})`; }; const objToRGB = (obj: rgb): string => { return `rgb(${obj.r},${obj.g},${obj.b})`; }; const rgbToObj = (rgbString: string): rgb => { let array: string[] = rgbString.split(','); return {r: Number(array[0].split('(')[1]), g: Number(array[1]), b: Number(array[2].slice(0, -1))}; };
-
添加 click 事件
pick.addEventListener('click', (ev) => { const target = <HTMLElement>ev.target; const x = ev.offsetX, y = ev.offsetY; switch (target.className) { case 'colorBar': colorBarThumb.style.top = y + 'px'; const result = changeColorBar(y / colorHeight); colorElement.style.backgroundColor = objToRGB(result); return changeColor(pxToNumber(colorPoint.style.left), pxToNumber(colorPoint.style.top)); case 'black': colorPoint.style.left = x + 'px'; colorPoint.style.top = y + 'px'; return changeColor(x, y); case 'transparencyBar': return changeTransparency(x); } }, false);
根据点击元素的 className 判断点击到的元素是什么,做出判断和对于的事件
4.1 先说下 colorBar 的选择
需要实现的是点击 bar 之后,根据点击的位置到bar顶部的长度,通过 bar 颜色的分布来判断出点击位置的颜色
听起来比较繁琐,其实还是很容易的
先获取鼠标点击距离顶端的长度:y = ev.offsetY
,改变 thumb 的位置;
colorBarRange 函数:const colorBarRange = (scale: number): colorBarRangeType => { switch (true) { case scale < .17: return {rank: scale / .17, arr: [{r: 255, g: 0, b: 0}, {r: 255, g: 255, b: 0}]}; case scale < .33: return {rank: (scale - .17) / .16, arr: [{r: 255, g: 255, b: 0}, {r: 0, g: 255, b: 0}]}; case scale < .5: return {rank: (scale - .33) / .17, arr: [{r: 0, g: 255, b: 0}, {r: 0, g: 255, b: 255}]}; case scale < .67: return {rank: (scale - .5) / .17, arr: [{r: 0, g: 255, b: 255}, {r: 0, g: 0, b: 255}]}; case scale < .83: return {rank: (scale - .67) / .16, arr: [{r: 0, g: 0, b: 255}, {r: 255, g: 0, b: 255}]}; default: return {rank: (scale - .83) / .17, arr: [{r: 255, g: 0, b: 255}, {r: 255, g: 0, b: 0}]}; } };
根据比例获取出颜色的域值和比例值
changeColorBar 函数:const changeColorBar = (scale: number) => { const range = colorBarRange(scale); let rangeArr: rgb[] = range.arr; let diff: rgb = { r: rangeArr[0].r - rangeArr[1].r, g: rangeArr[0].g - rangeArr[1].g, b: rangeArr[0].b - rangeArr[1].b }; let result = rangeArr[1]; for (let i in diff) { result[i] = result[i] + diff[i] * (1 - range.rank) | 0; } return result; };
通过上面的函数通过和255满值的计算,乘以比例值,获取出颜色值;
changeColor的作用是改变 black的值,具体下面会说
4.2 当前选中项为 black 时:
改变点的位置:colorPoint.style.left = x + 'px'; colorPoint.style.top = y + 'px';
再次调用
changeColor
函数const changeColor = (x: number = 0, y: number = 0): void => { let {r, g, b} = rgbToObj(colorElement.style.backgroundColor); const difference = { r: 255 - r, g: 255 - g, b: 255 - b }; const scaleX = x / colorWidth; scaleChange(difference, scaleX); const result = { r: 255 - difference.r, g: 255 - difference.g, b: 255 - difference.b }; const scaleY = y / colorHeight; scaleChange(result, 1 - scaleY); const RGBA = objToRGBA(result); chooseColor.style.backgroundColor = RGBA; rgbaText.value = RGBA; transparency.style.backgroundColor = objToRGB(result); };
传入2个值,一个是x,一个是 y, 各自对比 black 的长宽,得到比例,通过满值
rgb(255,255,255)
乘以比例, 获取 rgb 值,默认透明度为1,转换为 rgba 格式
4.3 当选中项为 transparency 时:
changeTransparency 函数:const changeTransparency = (x: number) => { const transparency = getTransparency(x); transparencyThumb.style.left = x + 'px'; transparencyCache = transparency; let currentColor = rgbaText.value.split(','); currentColor.splice(currentColor.length - 1, 1, transparency + ')'); const changeTransparencyColor = currentColor.join(','); rgbaText.value = changeTransparencyColor; chooseColor.style.backgroundColor = changeTransparencyColor; };
getTransparency
的作用是将宽度转换为比例,转化为满值为1,保留2位小数的值; -
鼠标移动
鼠标按下时改变值:pick.addEventListener('mousedown', (ev) => { const target = <HTMLElement>ev.target; switch (target.className) { case 'p': return isMoveColor = true; case 'point': return isMoveColor = true; case 'thumb bar': return isMoveColorBar = true; case 'thumb trans': return isMoveTransparency = true; } }, false);
监听
mousemove
事件:
5.1 当 isMoveColor 变化时:let diffX = cx - pickBoxOffsetLeft - 7, diffY = cy - pickBoxOffsetTop - 7; if (diffX < 0) diffX = 0; if (diffY < 0) diffY = 0; if (diffX > colorWidth) diffX = colorWidth; if (diffY > colorHeight) diffY = colorHeight; changeColor(diffX, diffY); colorPoint.style.left = diffX + 'px'; colorPoint.style.top = diffY + 'px';
检测值鼠标是否移动到了选择器外,并且更新颜色值
5.2 当 isMoveColorBar 变化时:let diffY = cy - pickBoxOffsetTop - 7; if (diffY < 0) diffY = 0; if (diffY > colorHeight) diffY = colorHeight; colorBarThumb.style.top = diffY + 'px'; const result = changeColorBar(diffY / colorHeight); colorElement.style.backgroundColor = objToRGB(result); changeColor(pxToNumber(colorPoint.style.left), pxToNumber(colorPoint.style.top));
同样地判断是否到边框外,以上面改变颜色值的方式同样地获取;
5.3 当 isMoveTransparency 变化时:let diffX = cx - pickBoxOffsetLeft - 7; if (diffX < 0) diffX = 0; if (diffX > transparencyBarWidth) diffX = transparencyBarWidth; changeTransparency(diffX);
检验是否到边框外即可;
-
结语
虽然现在很多情况下,不用写这个东西了,但其中还是蕴含了很多知识点的,尤其是要接触了图形的人,实现这的关键在于,将坐标转换为颜色这一步,如果能够搞懂,那么这么对于你来说就会显得异常简单了;
完;