实现一个颜色选择器

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

这次要写的是一个颜色选择器,也许很多人都认为是不需要的,因为有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 @   Grewer  阅读(840)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示