Vue取色器功能

2,345 阅读1分钟

基于vue,实现一个类似于ps取色器的功能,可用于多种颜色的自定义选色取值。


重点原理计算:主要是利用三层div的上下层级关系,底部是固定颜色的层(即是tempColor);中层是从左到右透明度逐渐从1到0的渐变,根据box宽度换算出该层当前触点的透明度值alpha;上层间是从下到上透明度逐渐从1到0的渐变,也是根据box宽度换算出该层当前触点的透明度值alpha;


  1. 计算中层跟上层alpha叠加之后新的alpha值,公式:
function new_alpha(a1, a2) {
   return 1 - (1 - a1) * (1 - a2);
},

  1. 计算两个颜色,一个为半透明的颜色,一个不透明的颜色,有层级关系,不透明的在下层(即c1),半透明的在上层(即c2)公式:

function getNewRgb(c1, alpha, c2) {
   return c1 * (1 - alpha) + c2 * alpha;
}
// 注意:这里的c1跟c2,传参时是一致的,好比都是r值,或都是g值,或都是b值。

  1. 最后需要再将中层跟上层的颜色再次进行换算。即是代码中的new_r_g_b方法。

<template>
    <div class="tricolor_wheel_box">
        <div class="inputBox">
            <div class="r">
                <span>r:</span>
                <input type="text" v-model="tempColor.r" />
            </div>
            <div class="g">
                <span>g:</span>
                <input type="text" v-model="tempColor.g" />
            </div>
            <div class="b">
                <span>b:</span>
                <input type="text" v-model="tempColor.b" />
            </div>
        </div>
        <div
            class="content"
            :style="{
                width: `${triColorWH.width}`,
                height: `${triColorWH.height}`,
                backgroundColor: `rgb(${tempColor.r},${tempColor.g},${tempColor.b})`
            }"
            @touchstart="Touchstart"
            @touchmove="Touchmove"
            @touchend="Touchend"
            ref="tri_color_ref"
        >
            <div class="color_inset1"></div>
            <div class="color_inset2"></div>
            <div
                class="dot"
                :style="{
                    top: `${dotPosition.top}px`,
                    left: `${dotPosition.left}px`,
                    width: `${contactWH.width}`,
                    height: `${contactWH.height}`
                }"
            ></div>
        </div>
        <div
            class="current_rgb"
            :style="{
                backgroundColor: `rgb(${newColor.r},${newColor.g},${newColor.b})`
            }"
        >
            <div class="msg">当前颜色:</div>
            <div class="rgb_text">
                {{ `rgb(${newColor.r},${newColor.g},${newColor.b})` }}
            </div>
        </div>
    </div>
</template>
<script>
export default {
    name: "tricolor-wheel-components",
    components: {},
    data() {
        return {
            Touch: {
                // 刚开始触摸的位置
                firstX: 0,
                firstY: 0,
                // 移动中的位置
                moveX: 0,
                moveY: 0,
                // 离开屏幕的位置
                endX: 0,
                endY: 0,
                // 手指与圆心之间的距离,,两点距离公式,,平方开方
                sqrtNum: 0,
                powX: 0,
                powY: 0
            },
            colorBoxInfo: {
                width: 0,
                height: 0,
                top: 0,
                left: 0,
                right: 0,
                bottom: 0
            },
            //输入固定的值,非透明,a固定为1
            tempColor: {
                r: 255,
                g: 0,
                b: 0,
                a: 1
            }
        };
    },
    props: {
        triColorWH: {
            type: Object,
            default: function() {
                return {
                    width: "50vw",
                    height: "50vw"
                };
            }
        },
        contactWH: {
            type: Object,
            default: function() {
                return {
                    width: "4vw",
                    height: "4vw"
                };
            }
        }
    },
    computed: {
        //触点的位置
        dotPosition() {
            let p = {
                top: 0,
                left: 0
            };
            //相对于取色器盒子的左上角为原点
            let x = this.Touch.moveX - this.colorBoxInfo.left;
            let y = this.Touch.moveY - this.colorBoxInfo.top;
            //触点超出取色器盒子无效
            x < 0 ? (x = 0) : "";
            y < 0 ? (y = 0) : "";
            this.colorBoxInfo.width < x ? (x = this.colorBoxInfo.width) : "";
            this.colorBoxInfo.height < y ? (y = this.colorBoxInfo.height) : "";
            p.top = y;
            p.left = x;
            return p;
        },
        //某个触点的白色层的颜色值, 根据触点位置换算出该点的透明度
        whiteColor() {
            let a = 1;
            //排除NAN的情况
            if (this.dotPosition.left === 0 && this.colorBoxInfo.width === 0) {
                a = 1;
            } else {
                a = 1 - this.dotPosition.left / this.colorBoxInfo.width;
            }
            return {
                r: 255,
                g: 255,
                b: 255,
                a
            };
        },
        //某个触点的黑色层的颜色值, 根据触点距离换算出该点的透明度
        blackColor() {
            let a = 1;
            //排除NAN的情况
            if (this.dotPosition.top === 0 && this.colorBoxInfo.height === 0) {
                a = 0;
            } else {
                a =
                    1 -
                    (this.colorBoxInfo.height - this.dotPosition.top) /
                        this.colorBoxInfo.height;
            }
            return {
                r: 0,
                g: 0,
                b: 0,
                a
            };
        },
        //白色黑色两个半透明的颜色叠加后的颜色值
        w_b_color() {
            return {
                r: this.new_r_g_b(
                    this.whiteColor.r,
                    this.whiteColor.a,
                    this.blackColor.r,
                    this.blackColor.a
                ),
                g: this.new_r_g_b(
                    this.whiteColor.g,
                    this.whiteColor.a,
                    this.blackColor.g,
                    this.blackColor.a
                ),
                b: this.new_r_g_b(
                    this.whiteColor.b,
                    this.whiteColor.a,
                    this.blackColor.b,
                    this.blackColor.a
                ),
                a: this.new_alpha(this.whiteColor.a, this.blackColor.a)
            };
        },
        //由黑白叠加出的w_b_color跟固定的不透明颜色再次叠加,得出最终的颜色
        newColor() {
            let r = this.getNewRgb(
                this.tempColor.r,
                this.w_b_color.a,
                this.w_b_color.r
            );
            let g = this.getNewRgb(
                this.tempColor.g,
                this.w_b_color.a,
                this.w_b_color.g
            );
            let b = this.getNewRgb(
                this.tempColor.b,
                this.w_b_color.a,
                this.w_b_color.b
            );
            r = Math.round(r);
            g = Math.round(g);
            b = Math.round(b);
            return {
                r,
                g,
                b
            };
        }
    },
    methods: {
        //计算两个半透明度颜色叠加后的颜色
        new_r_g_b(c1, a1, c2, a2) {
            let newColor = 0;
            let molecule = 1;
            let denominator = 1;
            molecule = c1 * a1 * (1 - a2) + c2 * a2;
            denominator = a1 + a2 - a1 * a2;
            //排除NAN的情况
            if (molecule === 0 && denominator === 0) {
                newColor = 255;
            } else {
                newColor = molecule / denominator;
            }
            return newColor;
        },
        new_alpha(a1, a2) {
            return 1 - (1 - a1) * (1 - a2);
        },
        getNewRgb(c1, alpha, c2) {
            return c1 * (1 - alpha) + c2 * alpha;
        },
        Touchstart(event) {
            event.preventDefault();
            this.Touch.firstX = event.targetTouches[0].clientX;
            this.Touch.firstY = event.targetTouches[0].clientY;
            const rectInfo = this.$refs.tri_color_ref.getBoundingClientRect();
            this.colorBoxInfo.width = rectInfo.width;
            this.colorBoxInfo.height = rectInfo.height;
            this.colorBoxInfo.top = rectInfo.top;
            this.colorBoxInfo.left = rectInfo.left;
            this.colorBoxInfo.right = rectInfo.right;
            this.colorBoxInfo.bottom = rectInfo.bottom;
            this.Touch.moveX = this.Touch.firstX;
            this.Touch.moveY = this.Touch.firstY;
        },
        Touchmove(event) {
            event.preventDefault();
            this.Touch.moveX = event.targetTouches[0].clientX;
            this.Touch.moveY = event.targetTouches[0].clientY;
        },
        Touchend(event) {
            this.Touch.endX = event.changedTouches[0].clientX;
            this.Touch.endY = event.changedTouches[0].clientY;
        }
    },
    mounted() {}
};
</script>
<style lang="scss" scoped>
.tricolor_wheel_box {
    .inputBox {
        .r,
        .g,
        .b {
            width: 100%;
            height: 30px;
            span {
                font-size: 16px;
                display: inline-block;
                width: 24px;
                height: inherit;
            }
            input {
                width: 60px;
                height: 24px;
                font-size: 14px;
            }
        }
    }
    .content {
        position: relative;
        top: 30px;
        left: 0;
        width: 60vw;
        height: 60vw;
        background: rgb(255, 0, 0);
        .color_inset1,
        .color_inset2 {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        .color_inset1 {
            background: linear-gradient(90deg, #fff, transparent);
        }
        .color_inset2 {
            background: linear-gradient(0deg, #000, transparent);
        }
        .dot {
            position: absolute;
            top: 0;
            left: 0;
            width: 4vw;
            height: 4vw;
            background-color: transparent;
            border-radius: 50%;
            border: 1px solid #000;
            box-sizing: border-box;
            transform: translate(-50%, -50%);
            box-shadow: 0px 0px 5px 2px rgba(200, 200, 200, 0.5);
        }
    }
    .current_rgb {
        width: 100px;
        height: 100px;
        border: 1px solid #333;
        margin: 50px 0 0 0;
        position: relative;
        .msg {
            position: absolute;
            left: 0;
            bottom: -26px;
        }
        .rgb_text {
            position: absolute;
            left: 0;
            bottom: -60px;
            width: 220px;
            height: 30px;
            border-radius: 6px;
            border: 1px solid rgba(142, 142, 142, 0.5);
            box-sizing: border-box;
            font-size: 24px;
        }
    }
}
</style>

注意:我使用的是touch事件处理的,各位同仁是PC端的话请自己替换成move


codepen链接https://codepen.io/maomaoliangliang/pen/XWRbWEv


最后,效果图:

vue取色器 .gif