实现一个颜色选择器

最近经历了一波辞职,找工作,搬家这样一个过程,所以没有空写博客,现在稳定了下来,写一下过年时写过的一些东西;

这次要写的是一个颜色选择器,也许很多人都认为是不需要的,因为有h5的 api 提供类似的功能,但是作为一个探索者,怎么能不直接实现一个呢

  1. 首先是样式的编写
    关于样式方面我仿照的是 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>
    
  2. css 的添加
    说到这里得说一个 css 的渐变色的使用:
    linear-gradient 该 API 可以让颜色变成渐变色,相当好用,现在使用率很高
    如这般使用:

    background: linear-gradient(to left, #3f87a6, #ebf8e1, #f69d3c);
    

    第一个参数是颜色旋转角度,后面的参数都是颜色,也可以加上比例

    完成后的样式,如下图:

  3. 初始化

    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))};
    };
    
  4. 添加 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位小数的值;

  5. 鼠标移动
    鼠标按下时改变值:

    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);
    

    检验是否到边框外即可;

  6. 结语
    虽然现在很多情况下,不用写这个东西了,但其中还是蕴含了很多知识点的,尤其是要接触了图形的人,实现这的关键在于,将坐标转换为颜色这一步,如果能够搞懂,那么这么对于你来说就会显得异常简单了;

demo: 点击查看
github: 点击查看

完;

posted @ 2018-03-26 17:51  Grewer  阅读(825)  评论(0编辑  收藏  举报