Based on VUE, to achieve a function similar to PS color picker, can be used for a variety of customized color selection value.


Key principle calculation: the main use of three layers of div hierarchy, the bottom is a fixed color layer (i.e., tempColor); The middle layer is a gradual change from left to right transparency from 1 to 0. According to the width of box, the transparency value alpha of the current contact of this layer is converted. The transparency of the upper layer gradually changes from 1 to 0 from bottom to top, and the transparency value alpha of the current contact of the layer is also converted according to the width of box.


  1. Calculate the new alpha value after stacking alpha of middle layer and upper layer, formula:
function new_alpha(a1, a2) {
   return 1 - (1 - a1) * (1 - a2);
},
Copy the code

  1. Calculate two colors, one is translucent, one is opaque, there is a hierarchy, opaque at the bottom (c1), translucent at the top (C2) formula:

function getNewRgb(c1, alpha, c2) { return c1 * (1 - alpha) + c2 * alpha; } // note that c1 and c2 are passed in the same way, such as r, g, or B.Copy the code

  1. Finally, the color of the middle layer and the upper layer should be converted again. This is the new_r_g_B method in the code.

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

Copy the code

Note: I use touch event processing, everyone is PC terminal, please replace yourself with move


Codepen link https://codepen.io/maomaoliangliang/pen/XWRbWEv


Finally, the renderings: